“This book is everything you never knew you wanted to know about JavaScript. Take your JavaScript code quality and productivity to the next level. David’s knowledge of the language, its intricacies and gotchas, is astounding, and it shines through in this truly definitive guide to the JavaScript language.”
Schalk Neethling, Senior Frontend Engineer at MDN Web Docs
“David Flanagan takes readers on a guided tour of JavaScript that will provide them with a feature-complete picture of the language and its ecosystem.”
Sarah Wachs, Frontend Developer and Women Who Code Berlin Lead
“Any developer interested in being productive in codebases developed throughout JavaScript’s lifetime (including the latest and emerging features) will be well served by a deep and reflective journey through this comprehensive and definitive book.”
Brian Sletten, President of Bosatsu Consulting
Seventh Edition
Master the World’s Most-Used Programming Language
Is used for emphasis and to indicate the first use of a term. Italic is also used for email addresses, URLs, and file names.
Constant widthIs used in all JavaScript code and CSS and HTML listings, and generally for anything that you would type literally when programming.
Constant width italicIs occasionally used when explaining JavaScript syntax.
Constant width boldShows commands or other text that should be typed literally by the user
$ node Welcome to Node.js v12.13.0. Type ".help" for more information. > .help .break Sometimes you get stuck, this gets you out .clear Alias for .break .editor Enter editor mode .exit Exit the repl .help Print this help message .load Load JS from a file into the REPL session .save Save all evaluated commands in this REPL session to a file Press ^C to abort current expression, ^D to exit the repl > let x = 2, y = 3; undefined > x + y 5 > (x === 2) && (y === 3) true > (x > 3) || (y < 3) false
$ node snippet.js
console.log("Hello World!");
<script src="hello.js"></script>
file:///Users/username/javascript/hello.html
// Anything following double slashes is an English-language comment.// Read the comments carefully: they explain the JavaScript code.// A variable is a symbolic name for a value.// Variables are declared with the let keyword:letx;// Declare a variable named x.// Values can be assigned to variables with an = signx=0;// Now the variable x has the value 0x// => 0: A variable evaluates to its value.// JavaScript supports several types of valuesx=1;// Numbers.x=0.01;// Numbers can be integers or reals.x="hello world";// Strings of text in quotation marks.x='JavaScript';// Single quote marks also delimit strings.x=true;// A Boolean value.x=false;// The other Boolean value.x=null;// Null is a special value that means "no value."x=undefined;// Undefined is another special value like null.
// JavaScript's most important datatype is the object.// An object is a collection of name/value pairs, or a string to value map.letbook={// Objects are enclosed in curly braces.topic:"JavaScript",// The property "topic" has value "JavaScript."edition:7// The property "edition" has value 7};// The curly brace marks the end of the object.// Access the properties of an object with . or []:book.topic// => "JavaScript"book["edition"]// => 7: another way to access property values.book.author="Flanagan";// Create new properties by assignment.book.contents={};// {} is an empty object with no properties.// Conditionally access properties with ?. (ES2020):book.contents?.ch01?.sect1// => undefined: book.contents has no ch01 property.// JavaScript also supports arrays (numerically indexed lists) of values:letprimes=[2,3,5,7];// An array of 4 values, delimited with [ and ].primes[0]// => 2: the first element (index 0) of the array.primes.length// => 4: how many elements in the array.primes[primes.length-1]// => 7: the last element of the array.primes[4]=9;// Add a new element by assignment.primes[4]=11;// Or alter an existing element by assignment.letempty=[];// [] is an empty array with no elements.empty.length// => 0// Arrays and objects can hold other arrays and objects:letpoints=[// An array with 2 elements.{x:0,y:0},// Each element is an object.{x:1,y:1}];letdata={// An object with 2 propertiestrial1:[[1,2],[3,4]],// The value of each property is an array.trial2:[[2,3],[4,5]]// The elements of the arrays are arrays.};
// Operators act on values (the operands) to produce a new value.// Arithmetic operators are some of the simplest:3+2// => 5: addition3-2// => 1: subtraction3*2// => 6: multiplication3/2// => 1.5: divisionpoints[1].x-points[0].x// => 1: more complicated operands also work"3"+"2"// => "32": + adds numbers, concatenates strings// JavaScript defines some shorthand arithmetic operatorsletcount=0;// Define a variablecount++;// Increment the variablecount--;// Decrement the variablecount+=2;// Add 2: same as count = count + 2;count*=3;// Multiply by 3: same as count = count * 3;count// => 6: variable names are expressions, too.// Equality and relational operators test whether two values are equal,// unequal, less than, greater than, and so on. They evaluate to true or false.letx=2,y=3;// These = signs are assignment, not equality testsx===y// => false: equalityx!==y// => true: inequalityx<y// => true: less-thanx<=y// => true: less-than or equalx>y// => false: greater-thanx>=y// => false: greater-than or equal"two"==="three"// => false: the two strings are different"two">"three"// => true: "tw" is alphabetically greater than "th"false===(x>y)// => true: false is equal to false// Logical operators combine or invert boolean values(x===2)&&(y===3)// => true: both comparisons are true. && is AND(x>3)||(y<3)// => false: neither comparison is true. || is OR!(x===y)// => true: ! inverts a boolean value
// Functions are parameterized blocks of JavaScript code that we can invoke.functionplus1(x){// Define a function named "plus1" with parameter "x"returnx+1;// Return a value one larger than the value passed in}// Functions are enclosed in curly bracesplus1(y)// => 4: y is 3, so this invocation returns 3+1letsquare=function(x){// Functions are values and can be assigned to varsreturnx*x;// Compute the function's value};// Semicolon marks the end of the assignment.square(plus1(y))// => 16: invoke two functions in one expression
constplus1=x=>x+1;// The input x maps to the output x + 1constsquare=x=>x*x;// The input x maps to the output x * xplus1(y)// => 4: function invocation is the samesquare(plus1(y))// => 16
// When functions are assigned to the properties of an object, we call// them "methods." All JavaScript objects (including arrays) have methods:leta=[];// Create an empty arraya.push(1,2,3);// The push() method adds elements to an arraya.reverse();// Another method: reverse the order of elements// We can define our own methods, too. The "this" keyword refers to the object// on which the method is defined: in this case, the points array from earlier.points.dist=function(){// Define a method to compute distance between pointsletp1=this[0];// First element of array we're invoked onletp2=this[1];// Second element of the "this" objectleta=p2.x-p1.x;// Difference in x coordinatesletb=p2.y-p1.y;// Difference in y coordinatesreturnMath.sqrt(a*a+// The Pythagorean theoremb*b);// Math.sqrt() computes the square root};points.dist()// => Math.sqrt(2): distance between our 2 points
// JavaScript statements include conditionals and loops using the syntax// of C, C++, Java, and other languages.functionabs(x){// A function to compute the absolute value.if(x>=0){// The if statement...returnx;// executes this code if the comparison is true.}// This is the end of the if clause.else{// The optional else clause executes its code ifreturn-x;// the comparison is false.}// Curly braces optional when 1 statement per clause.}// Note return statements nested inside if/else.abs(-10)===abs(10)// => truefunctionsum(array){// Compute the sum of the elements of an arrayletsum=0;// Start with an initial sum of 0.for(letxofarray){// Loop over array, assigning each element to x.sum+=x;// Add the element value to the sum.}// This is the end of the loop.returnsum;// Return the sum.}sum(primes)// => 28: sum of the first 5 primes 2+3+5+7+11functionfactorial(n){// A function to compute factorialsletproduct=1;// Start with a product of 1while(n>1){// Repeat statements in {} while expr in () is trueproduct*=n;// Shortcut for product = product * n;n--;// Shortcut for n = n - 1}// End of loopreturnproduct;// Return the product}factorial(4)// => 24: 1*4*3*2functionfactorial2(n){// Another version using a different loopleti,product=1;// Start with 1for(i=2;i<=n;i++)// Automatically increment i from 2 up to nproduct*=i;// Do this each time. {} not needed for 1-line loopsreturnproduct;// Return the factorial}factorial2(5)// => 120: 1*2*3*4*5
classPoint{// By convention, class names are capitalized.constructor(x,y){// Constructor function to initialize new instances.this.x=x;// This keyword is the new object being initialized.this.y=y;// Store function arguments as object properties.}// No return is necessary in constructor functions.distance(){// Method to compute distance from origin to point.returnMath.sqrt(// Return the square root of x² + y².this.x*this.x+// this refers to the Point object on whichthis.y*this.y// the distance method is invoked.);}}// Use the Point() constructor function with "new" to create Point objectsletp=newPoint(1,1);// The geometric point (1,1).// Now use a method of the Point object pp.distance()// => Math.SQRT2
Shows how JavaScript code in one file or script can use JavaScript functions and classes defined in other files or scripts.
Covers the built-in functions and classes that are available to all JavaScript programs. This includes important data stuctures like maps and sets, a regular expression class for textual pattern matching, functions for serializing JavaScript data structures, and much more.
Explains how the for/of loop works and how you can
make your own classes iterable with for/of. It also covers
generator functions and the yield statement.
This chapter is an in-depth exploration of asynchronous programming in
JavaScript, covering callbacks and events, Promise-based APIs, and
the async and await keywords. Although the core JavaScript
language is not asynchronous, asynchronous APIs are the default in
both web browsers and Node, and this chapter explains the techniques
for working with those APIs.
Introduces a number of advanced features of JavaScript that may be of interest to programmers writing libraries of code for other JavaScript programmers to use.
Introduces the web browser host environment, explains how web browsers execute JavaScript code, and covers the most important of the many APIs defined by web browsers. This is by far the longest chapter in the book.
Introduces the Node host environment, covering the fundamental programming model and the data structures and APIs that are most important to understand.
Covers tools and language extensions that are worth knowing about because they are widely used and may make you a more productive programmer.
$ node charfreq.js < charfreq.js T: ########### 11.22% E: ########## 10.15% R: ####### 6.68% S: ###### 6.44% A: ###### 6.16% N: ###### 5.81% O: ##### 5.45% I: ##### 4.54% H: #### 4.07% C: ### 3.36% L: ### 3.20% U: ### 3.08% /: ### 2.88%
/*** This Node program reads text from standard input, computes the frequency* of each letter in that text, and displays a histogram of the most* frequently used characters. It requires Node 12 or higher to run.** In a Unix-type environment you can invoke the program like this:* node charfreq.js < corpus.txt*/// This class extends Map so that the get() method returns the specified// value instead of null when the key is not in the mapclassDefaultMapextendsMap{constructor(defaultValue){super();// Invoke superclass constructorthis.defaultValue=defaultValue;// Remember the default value}get(key){if(this.has(key)){// If the key is already in the mapreturnsuper.get(key);// return its value from superclass.}else{returnthis.defaultValue;// Otherwise return the default value}}}// This class computes and displays letter frequency histogramsclassHistogram{constructor(){this.letterCounts=newDefaultMap(0);// Map from letters to countsthis.totalLetters=0;// How many letters in all}// This function updates the histogram with the letters of text.add(text){// Remove whitespace from the text, and convert to upper casetext=text.replace(/\s/g,"").toUpperCase();// Now loop through the characters of the textfor(letcharacteroftext){letcount=this.letterCounts.get(character);// Get old countthis.letterCounts.set(character,count+1);// Increment itthis.totalLetters++;}}// Convert the histogram to a string that displays an ASCII graphictoString(){// Convert the Map to an array of [key,value] arraysletentries=[...this.letterCounts];// Sort the array by count, then alphabeticallyentries.sort((a,b)=>{// A function to define sort order.if(a[1]===b[1]){// If the counts are the samereturna[0]<b[0]?-1:1;// sort alphabetically.}else{// If the counts differreturnb[1]-a[1];// sort by largest count.}});// Convert the counts to percentagesfor(letentryofentries){entry[1]=entry[1]/this.totalLetters*100;}// Drop any entries less than 1%entries=entries.filter(entry=>entry[1]>=1);// Now convert each entry to a line of textletlines=entries.map(([l,n])=>`${l}:${"#".repeat(Math.round(n))}${n.toFixed(2)}%`);// And return the concatenated lines, separated by newline characters.returnlines.join("\n");}}// This async (Promise-returning) function creates a Histogram object,// asynchronously reads chunks of text from standard input, and adds those chunks to// the histogram. When it reaches the end of the stream, it returns this histogramasyncfunctionhistogramFromStdin(){process.stdin.setEncoding("utf-8");// Read Unicode strings, not byteslethistogram=newHistogram();forawait(letchunkofprocess.stdin){histogram.add(chunk);}returnhistogram;}// This one final line of code is the main body of the program.// It makes a Histogram object from standard input, then prints the histogram.histogramFromStdin().then(histogram=>{console.log(histogram.toString());});
Case sensitivity, spaces, and line breaks
Comments
Literals
Identifiers and reserved words
Unicode
Optional semicolons
// This is a single-line comment./* This is also a comment */// and here is another comment./** This is a multi-line comment. The extra * characters at the start of* each line are not a required part of the syntax; they just look cool!*/
12// The number twelve1.2// The number one point two"hello world"// A string of text'Hi'// Another stringtrue// A Boolean valuefalse// The other Boolean valuenull// Absence of an object
imy_variable_namev13_dummy$str
as const export get null target void async continue extends if of this while await debugger false import return throw with break default finally in set true yield case delete for instanceof static try catch do from let super typeof class else function new switch var
enum implements interface package private protected public
constπ=3.14;constsí=true;
letcafé=1;// Define a variable using a Unicode charactercaf\u00e9// => 1; access the variable using an escape sequencecaf\u{E9}// => 1; another form of the same escape sequence
console.log("\u{1F600}");// Prints a smiley face emoji
constcafé=1;// This constant is named "caf\u{e9}"constcafé=2;// This constant is different: "cafe\u{301}"café// => 1: this constant has one valuecafé// => 2: this indistinguishable constant has a different value
a=3;b=4;
a=3;b=4;
letaa=3console.log(a)
leta;a=3;console.log(a);
lety=x+f(a+b).toString()
lety=x+f(a+b).toString();
letx=0// Semicolon omitted here;[x,x+1,x+2].forEach(console.log)// Defensive ; keeps this statement separate
returntrue;
return;true;
returntrue;
a.sort();// The object-oriented version of sort(a).
0310000000
0xff// => 255: (15*16 + 15)0xBADCAFE// => 195939070
0b10101 // => 21: (1*16 + 0*8 + 1*4 + 0*2 + 1*1) 0o377 // => 255: (3*64 + 7*8 + 7*1)
[digits][.digits][(E|e)[(+|-)]digits]
3.142345.6789.3333333333333333336.02e23// 6.02 × 10²³1.4738223E-32// 1.4738223 × 10⁻³²
Math.pow(2,53)// => 9007199254740992: 2 to the power 53Math.round(.6)// => 1.0: round to the nearest integerMath.ceil(.6)// => 1.0: round up to an integerMath.floor(.6)// => 0.0: round down to an integerMath.abs(-5)// => 5: absolute valueMath.max(x,y,z)// Return the largest argumentMath.min(x,y,z)// Return the smallest argumentMath.random()// Pseudo-random number x where 0 <= x < 1.0Math.PI// π: circumference of a circle / diameterMath.E// e: The base of the natural logarithmMath.sqrt(3)// => 3**0.5: the square root of 3Math.pow(3,1/3)// => 3**(1/3): the cube root of 3Math.sin(0)// Trigonometry: also Math.cos, Math.atan, etc.Math.log(10)// Natural logarithm of 10Math.log(100)/Math.LN10// Base 10 logarithm of 100Math.log(512)/Math.LN2// Base 2 logarithm of 512Math.exp(3)// Math.E cubed
Math.cbrt(27)// => 3: cube rootMath.hypot(3,4)// => 5: square root of sum of squares of all argumentsMath.log10(100)// => 2: Base-10 logarithmMath.log2(1024)// => 10: Base-2 logarithmMath.log1p(x)// Natural log of (1+x); accurate for very small xMath.expm1(x)// Math.exp(x)-1; the inverse of Math.log1p()Math.sign(x)// -1, 0, or 1 for arguments <, ==, or > 0Math.imul(2,3)// => 6: optimized multiplication of 32-bit integersMath.clz32(0xf)// => 28: number of leading zero bits in a 32-bit integerMath.trunc(3.9)// => 3: convert to an integer by truncating fractional partMath.fround(x)// Round to nearest 32-bit float numberMath.sinh(x)// Hyperbolic sine. Also Math.cosh(), Math.tanh()Math.asinh(x)// Hyperbolic arcsine. Also Math.acosh(), Math.atanh()
Infinity// A positive number too big to representNumber.POSITIVE_INFINITY// Same value1/0// => InfinityNumber.MAX_VALUE*2// => Infinity; overflow-Infinity// A negative number too big to representNumber.NEGATIVE_INFINITY// The same value-1/0// => -Infinity-Number.MAX_VALUE*2// => -InfinityNaN// The not-a-number valueNumber.NaN// The same value, written another way0/0// => NaNInfinity/Infinity// => NaNNumber.MIN_VALUE/2// => 0: underflow-Number.MIN_VALUE/2// => -0: negative zero-1/Infinity// -> -0: also negative 0-0// The following Number properties are defined in ES6Number.parseInt()// Same as the global parseInt() functionNumber.parseFloat()// Same as the global parseFloat() functionNumber.isNaN(x)// Is x the NaN value?Number.isFinite(x)// Is x a number and finite?Number.isInteger(x)// Is x an integer?Number.isSafeInteger(x)// Is x an integer -(2**53) < x < 2**53?Number.MIN_SAFE_INTEGER// => -(2**53 - 1)Number.MAX_SAFE_INTEGER// => 2**53 - 1Number.EPSILON// => 2**-52: smallest difference between numbers
letzero=0;// Regular zeroletnegz=-0;// Negative zerozero===negz// => true: zero and negative zero are equal1/zero===1/negz// => false: Infinity and -Infinity are not equal
letx=.3-.2;// thirty cents minus 20 centslety=.2-.1;// twenty cents minus 10 centsx===y// => false: the two values are not the same!x===.1// => false: .3-.2 is not equal to .1y===.1// => true: .2-.1 is equal to .1
1234n// A not-so-big BigInt literal0b111111n// A binary BigInt0o7777n// An octal BigInt0x8000000000000000n// => 2n**63n: A 64-bit integer
BigInt(Number.MAX_SAFE_INTEGER)// => 9007199254740991nletstring="1"+"0".repeat(100);// 1 followed by 100 zeros.BigInt(string)// => 10n**100n: one googol
1000n+2000n// => 3000n3000n-2000n// => 1000n2000n*3000n// => 6000000n3000n/997n// => 3n: the quotient is 33000n%997n// => 9n: and the remainder is 9(2n**131071n)-1n// A Mersenne prime with 39457 decimal digits
1<2n// => true2>1n// => true0==0n// => true0===0n// => false: the === checks for type equality as well
lettimestamp=Date.now();// The current time as a timestamp (a number).letnow=newDate();// The current time as a Date object.letms=now.getTime();// Convert to a millisecond timestamp.letiso=now.toISOString();// Convert to a string in standard format.
""// The empty string: it has zero characters'testing'"3.14"'name="myform"'"Wouldn't you prefer O'Reilly's book?""τ is the ratio of a circle's circumference to its radius"`"She said 'hi'", he said.`
// A string representing 2 lines written on one line:'two\nlines'// A one-line string written on 3 lines:"one\long\line"// A two-line string written on two lines:`the newline character at the end of this lineis included literally in this string`
<buttononclick="alert('Thank you')">Click Me</button>
'You\'re right, it can\'t be a quote'
| Sequence | Character represented |
|---|---|
|
The NUL character ( |
|
Backspace ( |
|
Horizontal tab ( |
|
Newline ( |
|
Vertical tab ( |
|
Form feed ( |
|
Carriage return ( |
|
Double quote ( |
|
Apostrophe or single quote ( |
|
Backslash ( |
|
The Unicode character specified by the two hexadecimal digits nn |
|
The Unicode character specified by the four hexadecimal digits nnnn |
|
The Unicode character specified by the codepoint n, where n is one to six hexadecimal digits between 0 and 10FFFF (ES6) |
letmsg="Hello, "+"world";// Produces the string "Hello, world"letgreeting="Welcome to my blog,"+" "+name;
s.length
lets="Hello, world";// Start with some text.// Obtaining portions of a strings.substring(1,4)// => "ell": the 2nd, 3rd, and 4th characters.s.slice(1,4)// => "ell": same things.slice(-3)// => "rld": last 3 characterss.split(", ")// => ["Hello", "world"]: split at delimiter string// Searching a strings.indexOf("l")// => 2: position of first letter ls.indexOf("l",3)// => 3: position of first "l" at or after 3s.indexOf("zz")// => -1: s does not include the substring "zz"s.lastIndexOf("l")// => 10: position of last letter l// Boolean searching functions in ES6 and laters.startsWith("Hell")// => true: the string starts with theses.endsWith("!")// => false: s does not end with thats.includes("or")// => true: s includes substring "or"// Creating modified versions of a strings.replace("llo","ya")// => "Heya, world"s.toLowerCase()// => "hello, world"s.toUpperCase()// => "HELLO, WORLD"s.normalize()// Unicode NFC normalization: ES6s.normalize("NFD")// NFD normalization. Also "NFKC", "NFKD"// Inspecting individual (16-bit) characters of a strings.charAt(0)// => "H": the first characters.charAt(s.length-1)// => "d": the last characters.charCodeAt(0)// => 72: 16-bit number at the specified positions.codePointAt(0)// => 72: ES6, works for codepoints > 16 bits// String padding functions in ES2017"x".padStart(3)// => " x": add spaces on the left to a length of 3"x".padEnd(3)// => "x ": add spaces on the right to a length of 3"x".padStart(3,"*")// => "**x": add stars on the left to a length of 3"x".padEnd(3,"-")// => "x--": add dashes on the right to a length of 3// Space trimming functions. trim() is ES5; others ES2019" test ".trim()// => "test": remove spaces at start and end" test ".trimStart()// => "test ": remove spaces on left. Also trimLeft" test ".trimEnd()// => " test": remove spaces at right. Also trimRight// Miscellaneous string methodss.concat("!")// => "Hello, world!": just use + operator instead"<>".repeat(5)// => "<><><><><>": concatenate n copies. ES6
lets="hello, world";s[0]// => "h"s[s.length-1]// => "d"
let s = `hello world`;
let name = "Bill";
let greeting = `Hello ${ name }.`; // greeting == "Hello Bill."
let errorMessage = `\
\u2718 Test failure at ${filename}:${linenumber}:
${exception.message}
Stack trace:
${exception.stack}
`;
`\n`.length// => 1: the string has a single newline characterString.raw`\n`.length// => 2: a backslash character and the letter n
/^HTML/;// Match the letters H T M L at the start of a string/[1-9][0-9]*/;// Match a nonzero digit, followed by any # of digits/\bjavascript\b/i;// Match "javascript" as a word, case-insensitive
lettext="testing: 1, 2, 3";// Sample textletpattern=/\d+/g;// Matches all instances of one or more digitspattern.test(text)// => true: a match existstext.search(pattern)// => 9: position of first matchtext.match(pattern)// => ["1", "2", "3"]: array of all matchestext.replace(pattern,"#")// => "testing: #, #, #"text.split(/\D+/)// => ["","1","2","3"]: split on nondigits
a===4
if(a===4){b=b+1;}else{a=a+1;}
undefinednull0-0NaN""// the empty string
if(o!==null)...
if(o)...
if((x===0&&y===0)||!(z===0)){// x and y are both zero or z is non-zero}
letstrname="string name";// A string to use as a property nameletsymname=Symbol("propname");// A Symbol to use as a property nametypeofstrname// => "string": strname is a stringtypeofsymname// => "symbol": symname is a symbolleto={};// Create a new objecto[strname]=1;// Define a property with a string nameo[symname]=2;// Define a property with a Symbol nameo[strname]// => 1: access the string-named propertyo[symname]// => 2: access the symbol-named property
lets=Symbol("sym_x");s.toString()// => "Symbol(sym_x)"
lets=Symbol.for("shared");lett=Symbol.for("shared");s===t// => trues.toString()// => "Symbol(shared)"Symbol.keyFor(t)// => "shared"
lets="hello";// Start with some lowercase texts.toUpperCase();// Returns "HELLO", but doesn't alter ss// => "hello": the original string has not changed
leto={x:1};// Start with an objecto.x=2;// Mutate it by changing the value of a propertyo.y=3;// Mutate it again by adding a new propertyleta=[1,2,3];// Arrays are also mutablea[0]=0;// Change the value of an array elementa[3]=4;// Add a new array element
leto={x:1},p={x:1};// Two objects with the same propertieso===p// => false: distinct objects are never equalleta=[],b=[];// Two distinct, empty arraysa===b// => false: distinct arrays are never equal
leta=[];// The variable a refers to an empty array.letb=a;// Now b refers to the same array.b[0]=1;// Mutate the array referred to by variable b.a[0]// => 1: the change is also visible through variable a.a===b// => true: a and b refer to the same object, so they are equal.
leta=["a","b","c"];// An array we want to copyletb=[];// A distinct array we'll copy intofor(leti=0;i<a.length;i++){// For each index of a[]b[i]=a[i];// Copy an element of a into b}letc=Array.from(b);// In ES6, copy arrays with Array.from()
functionequalArrays(a,b){if(a===b)returntrue;// Identical arrays are equalif(a.length!==b.length)returnfalse;// Different-size arrays not equalfor(leti=0;i<a.length;i++){// Loop through all elementsif(a[i]!==b[i])returnfalse;// If any differ, arrays not equal}returntrue;// Otherwise they are equal}
10+" objects"// => "10 objects": Number 10 converts to a string"7"*"4"// => 28: both strings convert to numbersletn=1-"x";// n == NaN; string "x" can't convert to a numbern+" objects"// => "NaN objects": NaN converts to string "NaN"
| Value | to String | to Number | to Boolean |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
see §3.9.3 |
see §3.9.3 |
|
|
|
|
|
|
|
|
|
|
use join() method |
|
|
|
see §3.9.3 |
|
|
null==undefined// => true: These two values are treated as equal."0"==0// => true: String converts to a number before comparing.0==false// => true: Boolean converts to number before comparing."0"==false// => true: Both operands convert to 0 before comparing!
Number("3")// => 3String(false)// => "false": Or use false.toString()Boolean([])// => true
x+""// => String(x)+x// => Number(x)x-0// => Number(x)!!x// => Boolean(x): Note double !
letn=17;letbinary="0b"+n.toString(2);// binary == "0b10001"letoctal="0o"+n.toString(8);// octal == "0o21"lethex="0x"+n.toString(16);// hex == "0x11"
letn=123456.789;n.toFixed(0)// => "123457"n.toFixed(2)// => "123456.79"n.toFixed(5)// => "123456.78900"n.toExponential(1)// => "1.2e+5"n.toExponential(3)// => "1.235e+5"n.toPrecision(4)// => "1.235e+5"n.toPrecision(7)// => "123456.8"n.toPrecision(10)// => "123456.7890"
parseInt("3 blind mice")// => 3parseFloat(" 3.14 meters")// => 3.14parseInt("-12.34")// => -12parseInt("0xFF")// => 255parseInt("0xff")// => 255parseInt("-0XFF")// => -255parseFloat(".1")// => 0.1parseInt("0.1")// => 0parseInt(".1")// => NaN: integers can't start with "."parseFloat("$72.47")// => NaN: numbers can't start with "$"
parseInt("11",2)// => 3: (1*2 + 1)parseInt("ff",16)// => 255: (15*16 + 15)parseInt("zz",36)// => 1295: (35*36 + 35)parseInt("077",8)// => 63: (7*8 + 7)parseInt("077",10)// => 77: (7*10 + 7)
This algorithm returns a primitive value, preferring a string value, if a conversion to string is possible.
This algorithm returns a primitive value, preferring a number, if such a conversion is possible.
This algorithm expresses no preference about what type of primitive value is desired, and classes can define their own conversions. Of the built-in JavaScript types, all except Date implement this algorithm as prefer-number. The Date class implements this algorithm as prefer-string.
({x:1,y:2}).toString()// => "[object Object]"
[1,2,3].toString()// => "1,2,3"(function(x){f(x);}).toString()// => "function(x) { f(x); }"/\d+/g.toString()// => "/\\d+/g"letd=newDate(2020,0,1);d.toString()// => "Wed Jan 01 2020 00:00:00 GMT-0800 (Pacific Standard Time)"
letd=newDate(2010,0,1);// January 1, 2010, (Pacific time)d.valueOf()// => 1262332800000
The prefer-string algorithm first tries the toString()
method. If the method is defined and returns a primitive value, then
JavaScript uses that primitive value (even if it is not a
string!). If toString() does not exist or if it returns an object,
then JavaScript tries the valueOf() method. If that method exists
and returns a primitive value, then JavaScript uses that
value. Otherwise, the conversion fails with a TypeError.
The prefer-number algorithm works like the prefer-string
algorithm, except that it tries valueOf() first and toString()
second.
The no-preference algorithm depends on the class of the object being converted. If the object is a Date object, then JavaScript uses the prefer-string algorithm. For any other object, JavaScript uses the prefer-number algorithm.
Number([])// => 0: this is unexpected!Number([99])// => 99: really?
leti;letsum;
leti,sum;
letmessage="hello";leti=0,j=0,k=0;letx=2,y=x*x;// Initializers can use previously declared variables
constH0=74;// Hubble constant (km/s/Mpc)constC=299792.458;// Speed of light in a vacuum (km/s)constAU=1.496E8;// Astronomical Unit: distance to the sun (km)
for(leti=0,len=data.length;i<len;i++)console.log(data[i]);for(letdatumofdata)console.log(datum);for(letpropertyinobject)console.log(property);
for(constdatumofdata)console.log(datum);for(constpropertyinobject)console.log(property);
constx=1;// Declare x as a global constantif(x===1){letx=2;// Inside a block x can refer to a different valueconsole.log(x);// Prints 2}console.log(x);// Prints 1: we're back in the global scope nowletx=3;// ERROR! Syntax error trying to re-declare x
leti=10;i="ten";
varx;vardata=[],count=data.length;for(vari=0;i<count;i++)console.log(data[i]);
Variables declared with var do not have block scope. Instead, they
are scoped to the body of the containing function no matter how deeply
nested they are inside that function.
If you use var outside of a function body, it declares a global
variable. But global variables declared with var differ from
globals declared with let in an important way. Globals declared
with var are implemented as properties of the global object
(§3.7). The global object can be referenced as
globalThis. So if you write var x = 2; outside of a function, it
is like you wrote globalThis.x = 2;. Note however, that the analogy is not perfect: the properties created with global var declarations
cannot be deleted with the delete operator
(§4.13.4). Global variables and constants declared with
let and const are not properties of the global object.
Unlike variables declared with let, it is legal to declare the same
variable multiple times with var. And because var variables have
function scope instead of block scope, it is actually common to do
this kind of redeclaration. The variable i is frequently used for
integer values, and especially as the index variable of for
loops. In a function with multiple for loops, it is typical for each
one to begin for(var i = 0; .... Because var does not scope these
variables to the loop body, each of these loops is (harmlessly)
re-declaring and re-initializing the same variable.
One of the most unusual features of var declarations is known as
hoisting. When a variable is declared with var, the declaration is
lifted up (or “hoisted”) to the top of the enclosing function. The
initialization of the variable remains where you wrote it, but the
definition of the variable moves to the top of the function. So
variables declared with var can be used, without error, anywhere in
the enclosing function. If the initialization code has not run yet,
then the value of the variable may be undefined, but you won’t get an
error if you use the variable before it is initialized. (This can be a
source of bugs and is one of the important misfeatures that let
corrects: if you declare a variable with let but attempt to use it
before the let statement runs, you will get an actual error instead
of just seeing an undefined value.)
let[x,y]=[1,2];// Same as let x=1, y=2[x,y]=[x+1,y+1];// Same as x = x + 1, y = y + 1[x,y]=[y,x];// Swap the value of the two variables[x,y]// => [3,2]: the incremented and swapped values
// Convert [x,y] coordinates to [r,theta] polar coordinatesfunctiontoPolar(x,y){return[Math.sqrt(x*x+y*y),Math.atan2(y,x)];}// Convert polar to Cartesian coordinatesfunctiontoCartesian(r,theta){return[r*Math.cos(theta),r*Math.sin(theta)];}let[r,theta]=toPolar(1.0,1.0);// r == Math.sqrt(2); theta == Math.PI/4let[x,y]=toCartesian(r,theta);// [x, y] == [1.0, 1,0]
leto={x:1,y:2};// The object we'll loop overfor(const[name,value]ofObject.entries(o)){console.log(name,value);// Prints "x 1" and "y 2"}
let[x,y]=[1];// x == 1; y == undefined[x,y]=[1,2,3];// x == 1; y == 2[,x,,y]=[1,2,3,4];// x == 2; y == 4
let[x,...y]=[1,2,3,4];// y == [2,3,4]
let[a,[b,c]]=[1,[2,2.5],3];// a == 1; b == 2; c == 2.5
let[first,...rest]="Hello";// first == "H"; rest == ["e","l","l","o"]
lettransparent={r:0.0,g:0.0,b:0.0,a:1.0};// A RGBA colorlet{r,g,b}=transparent;// r == 0.0; g == 0.0; b == 0.0
// Same as const sin=Math.sin, cos=Math.cos, tan=Math.tanconst{sin,cos,tan}=Math;
// Same as const cosine = Math.cos, tangent = Math.tan;const{cos:cosine,tan:tangent}=Math;
letpoints=[{x:1,y:2},{x:3,y:4}];// An array of two point objectslet[{x:x1,y:y1},{x:x2,y:y2}]=points;// destructured into 4 variables.(x1===1&&y1===2&&x2===3&&y2===4)// => true
letpoints={p1:[1,2],p2:[3,4]};// An object with 2 array propslet{p1:[x1,y1],p2:[x2,y2]}=points;// destructured into 4 vars(x1===1&&y1===2&&x2===3&&y2===4)// => true
How to write and manipulate numbers and strings of text in JavaScript.
How to work with JavaScript’s other primitive types: booleans,
Symbols, null, and undefined.
The differences between immutable primitive types and mutable reference types.
How JavaScript converts values implicitly from one type to another and how you can do so explicitly in your programs.
How to declare and initialize constants and variables (including with destructuring assignment) and the lexical scope of the variables and constants you declare.
1.23// A number literal"hello"// A string literal/pattern/// A regular expression literal
true// Evalutes to the boolean true valuefalse// Evaluates to the boolean false valuenull// Evaluates to the null valuethis// Evaluates to the "current" object
i// Evaluates to the value of the variable i.sum// Evaluates to the value of the variable sum.undefined// The value of the "undefined" property of the global object
[]// An empty array: no expressions inside brackets means no elements[1+2,3+4]// A 2-element array. First element is 3, second is 7
letmatrix=[[1,2,3],[4,5,6],[7,8,9]];
letsparseArray=[1,,,,5];
letp={x:2.3,y:-1.2};// An object with 2 propertiesletq={};// An empty object with no propertiesq.x=2.3;q.y=-1.2;// Now q has the same properties as p
letrectangle={upperLeft:{x:2,y:2},lowerRight:{x:4,y:5}};
// This function returns the square of the value passed to it.letsquare=function(x){returnx*x;};
expression . identifier
expression [ expression ]
leto={x:1,y:{z:3}};// An example objectleta=[o,4,[5,6]];// An example array that contains the objecto.x// => 1: property x of expression oo.y.z// => 3: property z of expression o.yo["x"]// => 1: property x of object oa[1]// => 4: element at index 1 of expression aa[2]["1"]// => 6: element at index 1 of expression a[2]a[0].x// => 1: property x of expression a[0]
expression ?. identifier
expression ?.[ expression ]
leta={b:null};a.b?.c.d// => undefined
leta={b:{}};a.b?.c?.d// => undefined
leta;// Oops, we forgot to initialize this variable!letindex=0;try{a[index++];// Throws TypeError}catch(e){index// => 1: increment occurs before TypeError is thrown}a?.[index++]// => undefined: because a is undefinedindex// => 1: not incremented because ?.[] short-circuitsa[index++]// !TypeError: can't index undefined.
f(0)// f is the function expression; 0 is the argument expression.Math.max(x,y,z)// Math.max is the function; x, y, and z are the arguments.a.sort()// a.sort is the function; there are no arguments.
functionsquare(x,log){// The second argument is an optional functionif(log){// If the optional function is passedlog(x);// Invoke it}returnx*x;// Return the square of the argument}
functionsquare(x,log){// The second argument is an optional functionlog?.(x);// Call the function if there is onereturnx*x;// Return the square of the argument}
letf=null,x=0;try{f(x++);// Throws TypeError because f is null}catch(e){x// => 1: x gets incremented before the exception is thrown}f?.(x++)// => undefined: f is null, but no exception thrownx// => 1: increment is skipped because of short-circuiting
o.m()// Regular property access, regular invocationo?.m()// Conditional property access, regular invocationo.m?.()// Regular property access, conditional invocation
newObject()newPoint(2,3)
newObjectnewDate
| Operator | Operation | A | N | Types |
|---|---|---|---|---|
|
Pre- or post-increment |
R |
1 |
lval→num |
|
Pre- or post-decrement |
R |
1 |
lval→num |
|
Negate number |
R |
1 |
num→num |
|
Convert to number |
R |
1 |
any→num |
|
Invert bits |
R |
1 |
int→int |
|
Invert boolean value |
R |
1 |
bool→bool |
|
Remove a property |
R |
1 |
lval→bool |
|
Determine type of operand |
R |
1 |
any→str |
|
Return undefined value |
R |
1 |
any→undef |
|
Exponentiate |
R |
2 |
num,num→num |
|
Multiply, divide, remainder |
L |
2 |
num,num→num |
|
Add, subtract |
L |
2 |
num,num→num |
|
Concatenate strings |
L |
2 |
str,str→str |
|
Shift left |
L |
2 |
int,int→int |
|
Shift right with sign extension |
L |
2 |
int,int→int |
|
Shift right with zero extension |
L |
2 |
int,int→int |
|
Compare in numeric order |
L |
2 |
num,num→bool |
|
Compare in alphabetical order |
L |
2 |
str,str→bool |
|
Test object class |
L |
2 |
obj,func→bool |
|
Test whether property exists |
L |
2 |
any,obj→bool |
|
Test for non-strict equality |
L |
2 |
any,any→bool |
|
Test for non-strict inequality |
L |
2 |
any,any→bool |
|
Test for strict equality |
L |
2 |
any,any→bool |
|
Test for strict inequality |
L |
2 |
any,any→bool |
|
Compute bitwise AND |
L |
2 |
int,int→int |
|
Compute bitwise XOR |
L |
2 |
int,int→int |
|
Compute bitwise OR |
L |
2 |
int,int→int |
|
Compute logical AND |
L |
2 |
any,any→any |
|
Compute logical OR |
L |
2 |
any,any→any |
|
Choose 1st defined operand |
L |
2 |
any,any→any |
|
Choose 2nd or 3rd operand |
R |
3 |
bool,any,any→any |
|
Assign to a variable or property |
R |
2 |
lval,any→any |
|
Operate and assign |
R |
2 |
lval,any→any |
|
||||
|
||||
|
Discard 1st operand, return 2nd |
L |
2 |
any,any→any |
w=x+y*z;
w=(x+y)*z;
// my is an object with a property named functions whose value is an// array of functions. We invoke function number x, passing it argument// y, and then we ask for the type of the value returned.typeofmy.functions[x](y)
w=x-y-z;
w=((x-y)-z);
y=a**b**c;x=~-y;w=x=y=z;q=a?b:c?d:e?f:g;
y=(a**(b**c));x=~(-y);w=(x=(y=z));q=a?b:(c?d:(e?f:g));
1+2// => 3"hello"+" "+"there"// => "hello there""1"+"2"// => "12"
If either of its operand values is an object, it converts it to a
primitive using the object-to-primitive algorithm described in
§3.9.3. Date objects are converted by their toString() method,
and all other objects are converted via valueOf(), if that method
returns a primitive value. However, most objects do not have a useful
valueOf() method, so they are converted via toString() as
well.
After object-to-primitive conversion, if either operand is a string, the other is converted to a string and concatenation is performed.
Otherwise, both operands are converted to numbers (or to NaN) and
addition is performed.
1+2// => 3: addition"1"+"2"// => "12": concatenation"1"+2// => "12": concatenation after number-to-string1+{}// => "1[object Object]": concatenation after object-to-stringtrue+true// => 2: addition after boolean-to-number2+null// => 2: addition after null converts to 02+undefined// => NaN: addition after undefined converts to NaN
1+2+" blind mice"// => "3 blind mice"1+(2+" blind mice")// => "12 blind mice"
+)The unary plus operator converts its operand to
a number (or to NaN) and returns that converted value. When used
with an operand that is already a number, it doesn’t do anything. This
operator may not be used with BigInt values, since they cannot be
converted to regular numbers.
-)When - is used as a unary operator, it
converts its operand to a number, if necessary, and then changes the
sign of the result.
++)The ++ operator increments (i.e., adds 1 to)
its single operand, which must be an lvalue (a variable, an element of
an array, or a property of an object). The operator converts its
operand to a number, adds 1 to that number, and assigns the
incremented value back into the variable, element, or property.
The return value of the ++ operator depends on its position
relative to the operand. When used before the operand, where it is
known as the pre-increment operator, it increments the operand and
evaluates to the incremented value of that operand. When used after the
operand, where it is known as the post-increment operator, it
increments its operand but evaluates to the unincremented value of
that operand. Consider the difference between these two lines of code:
leti=1,j=++i;// i and j are both 2letn=1,m=n++;// n is 2, m is 1
Note that the expression x++ is not always the same as
x=x+1. The ++ operator never performs string
concatenation: it always converts its operand to a number and
increments it. If x is the string “1”, ++x is the number 2,
but x+1 is the string “11”.
Also note that, because of JavaScript’s automatic semicolon insertion, you cannot insert a line break between the post-increment operator and the operand that precedes it. If you do so, JavaScript will treat the operand as a complete statement by itself and insert a semicolon before it.
This operator, in both its pre- and post-increment forms, is most
commonly used to increment a counter that controls a for loop
(§5.4.3).
--)The -- operator expects an lvalue operand. It
converts the value of the operand to a number, subtracts 1, and
assigns the decremented value back to the operand. Like the ++
operator, the return value of -- depends on its position relative to
the operand. When used before the operand, it decrements and returns
the decremented value. When used after the operand, it decrements the
operand but returns the undecremented value. When used after its
operand, no line break is allowed between the operand and the
operator.
&)The & operator performs a Boolean AND
operation on each bit of its integer arguments. A bit is set in the
result only if the corresponding bit is set in both operands. For
example, 0x1234 & 0x00FF evaluates to 0x0034.
|)The | operator performs a Boolean OR
operation on each bit of its integer arguments. A bit is set in the
result if the corresponding bit is set in one or both of the
operands. For example, 0x1234 | 0x00FF evaluates to 0x12FF.
^)The ^ operator performs a Boolean exclusive
OR operation on each bit of its integer arguments. Exclusive OR means
that either operand one is true or operand two is true, but not
both. A bit is set in this operation’s result if a corresponding bit
is set in one (but not both) of the two operands. For example, 0xFF00
^ 0xF0F0 evaluates to 0x0FF0.
~)The ~ operator is a unary operator that
appears before its single integer operand. It operates by reversing
all bits in the operand. Because of the way signed integers are
represented in JavaScript, applying the ~ operator to a value is
equivalent to changing its sign and subtracting 1. For example, ~0x0F
evaluates to 0xFFFFFFF0, or −16.
<<)The << operator moves all bits in its first
operand to the left by the number of places specified in the second
operand, which should be an integer between 0 and 31. For example, in
the operation a << 1, the first bit (the ones bit) of a becomes
the second bit (the twos bit), the second bit of a becomes the
third, etc. A zero is used for the new first bit, and the value of the
32nd bit is lost. Shifting a value left by one position is equivalent
to multiplying by 2, shifting two positions is equivalent to
multiplying by 4, and so on. For example, 7 << 2 evaluates to 28.
>>)The >> operator moves all bits in
its first operand to the right by the number of places specified in
the second operand (an integer between 0 and 31). Bits that are
shifted off the right are lost. The bits filled in on the left depend
on the sign bit of the original operand, in order to preserve the sign
of the result. If the first operand is positive, the result has zeros
placed in the high bits; if the first operand is negative, the result
has ones placed in the high bits. Shifting a positive value right one
place is equivalent to dividing by 2 (discarding the remainder),
shifting right two places is equivalent to integer division by 4, and
so on. 7 >> 1 evaluates to 3, for example, but note that and −7 >>
1 evaluates to −4.
>>>)The >>> operator is just
like the >> operator, except that the bits shifted in on the left
are always zero, regardless of the sign of the first operand. This is
useful when you want to treat signed 32-bit values as if they are
unsigned integers. −1 >> 4 evaluates to −1, but −1 >>> 4 evaluates
to 0x0FFFFFFF, for example. This is the only one of the JavaScript
bitwise operators that cannot be used with BigInt values. BigInt does
not represent negative numbers by setting the high bit the way that
32-bit integers do, and this operator only makes sense for that
particular two’s complement representation.
If the two values have different types, they are not equal.
If both values are null or both values are undefined, they are
equal.
If both values are the boolean value true or both are the boolean
value false, they are equal.
If one or both values is NaN, they are not equal. (This is
surprising, but the NaN value is never equal to any other value,
including itself! To check whether a value x is NaN, use x !==
x, or the global isNaN() function.)
If both values are numbers and have the same value, they are equal.
If one value is 0 and the other is -0, they are also equal.
If both values are strings and contain exactly the same 16-bit values
(see the sidebar in §3.3) in the same positions, they are equal. If
the strings differ in length or content, they are not equal. Two
strings may have the same meaning and the same visual appearance, but
still be encoded using different sequences of 16-bit values. JavaScript
performs no Unicode normalization, and a pair of strings like this is
not considered equal to the === or == operators.
If both values refer to the same object, array, or function, they are equal. If they refer to different objects, they are not equal, even if both objects have identical properties.
If the two values have the same type, test them for strict equality as described previously. If they are strictly equal, they are equal. If they are not strictly equal, they are not equal.
If the two values do not have the same type, the == operator may
still consider them equal. It uses the following rules and type
conversions to check for equality:
If one value is null and the other is undefined, they are equal.
If one value is a number and the other is a string, convert the string to a number and try the comparison again, using the converted value.
If either value is true, convert it to 1 and try the comparison
again. If either value is false, convert it to 0 and try the
comparison again.
If one value is an object and the other is a number or string,
convert the object to a primitive using the algorithm described in
§3.9.3 and try the comparison again. An object is converted to a
primitive value by either its toString() method or its valueOf()
method. The built-in classes of core JavaScript attempt valueOf()
conversion before toString() conversion, except for the Date class,
which performs toString() conversion.
Any other combinations of values are not equal.
"1"==true// => true
<) The < operator evaluates to true if its first operand is less
than its second operand; otherwise, it evaluates to false.
>) The > operator evaluates to true if its first operand is
greater than its second operand; otherwise, it evaluates to false.
<=) The <= operator evaluates to true if its first operand is less
than or equal to its second operand; otherwise, it evaluates to false.
>=) The >= operator evaluates to true if its first operand is
greater than or equal to its second operand; otherwise, it evaluates to
false.
If either operand evaluates to an object, that object is converted to
a primitive value, as described at the end of §3.9.3; if its
valueOf() method returns a primitive value, that value is used.
Otherwise, the return value of its toString() method is used.
If, after any required object-to-primitive conversion, both operands are strings, the two strings are compared, using alphabetical order, where “alphabetical order” is defined by the numerical order of the 16-bit Unicode values that make up the strings.
If, after object-to-primitive conversion, at least one operand is not
a string, both operands are converted to numbers and compared
numerically. 0 and -0 are considered equal. Infinity is larger
than any number other than itself, and -Infinity is smaller than any
number other than itself. If either operand is (or converts to) NaN,
then the comparison operator always returns false. Although the
arithmetic operators do not allow BigInt values to be mixed with
regular numbers, the comparison operators do allow comparisons between
numbers and BigInts.
1+2// => 3: addition."1"+"2"// => "12": concatenation."1"+2// => "12": 2 is converted to "2".11<3// => false: numeric comparison."11"<"3"// => true: string comparison."11"<3// => false: numeric comparison, "11" converted to 11."one"<3// => false: numeric comparison, "one" converted to NaN.
letpoint={x:1,y:1};// Define an object"x"inpoint// => true: object has property named "x""z"inpoint// => false: object has no "z" property."toString"inpoint// => true: object inherits toString methodletdata=[7,8,9];// An array with elements (indices) 0, 1, and 2"0"indata// => true: array has an element "0"1indata// => true: numbers are converted to strings3indata// => false: no element 3
letd=newDate();// Create a new object with the Date() constructordinstanceofDate// => true: d was created with Date()dinstanceofObject// => true: all objects are instances of ObjectdinstanceofNumber// => false: d is not a Number objectleta=[1,2,3];// Create an array with array literal syntaxainstanceofArray// => true: a is an arrayainstanceofObject// => true: all arrays are objectsainstanceofRegExp// => false: arrays are not regular expressions
x===0&&y===0// true if, and only if, x and y are both 0
leto={x:1};letp=null;o&&o.x// => 1: o is truthy, so return value of o.xp&&p.x// => null: p is falsy, so return it and don't evaluate p.x
if(a===b)stop();// Invoke stop() only if a === b(a===b)&&stop();// This does the same thing
// If maxWidth is truthy, use that. Otherwise, look for a value in// the preferences object. If that is not truthy, use a hardcoded constant.letmax=maxWidth||preferences.maxWidth||500;
// Copy the properties of o to p, and return pfunctioncopy(o,p){p=p||{};// If no object passed for p, use a newly created object.// function body goes here}
// DeMorgan's Laws!(p&&q)===(!p||!q)// => true: for all values of p and q!(p||q)===(!p&&!q)// => true: for all values of p and q
i=0;// Set the variable i to 0.o.x=1;// Set the property x of object o to 1.
(a=b)===0
i=j=k=0;// Initialize 3 variables to 0
total+=salesTax;
total=total+salesTax;
| Operator | Example | Equivalent |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a op= b
a = a op b
data[i++]*=2;data[i++]=data[i++]*2;
eval("3+2")// => 5
eval("function f() { return x+1; }");
constgeval=eval;// Using another name does a global evalletx="global",y="global";// Two global variablesfunctionf(){// This function does a local evalletx="local";// Define a local variableeval("x += 'changed';");// Direct eval sets local variablereturnx;// Return changed local variable}functiong(){// This function does a global evallety="local";// A local variablegeval("y += 'changed';");// Indirect eval sets global variablereturny;// Return unchanged local variable}console.log(f(),x);// Local variable changed: prints "localchanged global":console.log(g(),y);// Global variable changed: prints "local globalchanged":
x>0?x:-x// The absolute value of x
greeting="hello "+(username?username:"there");
greeting="hello ";if(username){greeting+=username;}else{greeting+="there";}
(a!==null&&a!==undefined)?a:b
// If maxWidth is truthy, use that. Otherwise, look for a value in// the preferences object. If that is not truthy, use a hardcoded constant.letmax=maxWidth||preferences.maxWidth||500;
// If maxWidth is defined, use that. Otherwise, look for a value in// the preferences object. If that is not defined, use a hardcoded constant.letmax=maxWidth??preferences.maxWidth??500;
letoptions={timeout:0,title:"",verbose:false,n:null};options.timeout??1000// => 0: as defined in the objectoptions.title??"Untitled"// => "": as defined in the objectoptions.verbose??true// => false: as defined in the objectoptions.quiet??false// => false: property is not definedoptions.n??10// => 10: property is null
(a??b)||c// ?? first, then ||a??(b||c)// || first, then ??a??b||c// SyntaxError: parentheses are required
x |
typeof x |
|---|---|
|
|
|
|
|
|
any number or |
|
any BigInt |
|
any string |
|
any symbol |
|
any function |
|
any nonfunction object |
|
// If the value is a string, wrap it in quotes, otherwise, convert(typeofvalue==="string")?"'"+value+"'":value.toString()
leto={x:1,y:2};// Start with an objectdeleteo.x;// Delete one of its properties"x"ino// => false: the property does not exist anymoreleta=[1,2,3];// Start with an arraydeletea[2];// Delete the last element of the array2ina// => false: array element 2 doesn't exist anymorea.length// => 3: note that array length doesn't change, though
leto={x:1,y:2};deleteo.x;// Delete one of the object properties; returns true.typeofo.x;// Property does not exist; returns "undefined".deleteo.x;// Delete a nonexistent property; returns true.delete1;// This makes no sense, but it just returns true.// Can't delete a variable; returns false, or SyntaxError in strict mode.deleteo;// Undeletable property: returns false, or TypeError in strict mode.deleteObject.prototype;
letcounter=0;constincrement=()=>voidcounter++;increment()// => undefinedcounter// => 1
i=0,j=1,k=2;
i=0;j=1;k=2;
// The first comma below is part of the syntax of the let statement// The second comma is the comma operator: it lets us squeeze 2// expressions (i++ and j--) into a statement (the for loop) that expects 1.for(leti=0,j=10;i<j;i++,j--){console.log(i+j);}
Expressions are the phrases of a JavaScript program.
Any expression can be evaluated to a JavaScript value.
Expressions can also have side effects (such as variable assignment) in addition to producing a value.
Simple expressions such as literals, variable references, and property accesses can be combined with operators to produce larger expressions.
JavaScript defines operators for arithmetic, comparisons, Boolean logic, assignment, and bit manipulation, along with some miscellaneous operators, including the ternary conditional operator.
The JavaScript + operator is used to both add numbers and
concatenate strings.
The logical operators && and || have special “short-circuiting”
behavior and sometimes only evaluate one of their arguments. Common
JavaScript idioms require you to understand the special behavior of
these operators.
Statements like if and switch that make the
JavaScript interpreter execute or skip other statements depending on
the value of an expression
Statements like while and for that execute other
statements repetitively
Statements like break, return, and throw that cause
the interpreter to jump to another part of the program
greeting="Hello "+name;i*=3;
counter++;
deleteo.x;
console.log(debugMessage);displaySpinner();// A hypothetical function to display a spinner in a web app.
Math.cos(x);
cx=Math.cos(x);
{x=Math.PI;cx=Math.cos(x);console.log("cos(π) = "+cx);}
;
// Initialize an array afor(leti=0;i<a.length;a[i++]=0);
if((a===0)||(b===0));// Oops! This line does nothing...o=null;// and this line is always executed.
for(leti=0;i<a.length;a[i++]=0)/* empty */;
if (expression)statement
if(username==null)// If username is null or undefined,username="John Doe";// define it
// If username is null, undefined, false, 0, "", or NaN, give it a new valueif(!username)username="John Doe";
if(!address){address="";message="Please specify a mailing address.";}
if (expression)statement1elsestatement2
if(n===1)console.log("You have 1 new message.");elseconsole.log(`You have${n}new messages.`);
i=j=1;k=2;if(i===j)if(j===k)console.log("i equals k");elseconsole.log("i doesn't equal j");// WRONG!!
if(i===j){if(j===k)console.log("i equals k");elseconsole.log("i doesn't equal j");// OOPS!}
if(i===j){if(j===k){console.log("i equals k");}}else{// What a difference the location of a curly brace makes!console.log("i doesn't equal j");}
if(n===1){// Execute code block #1}elseif(n===2){// Execute code block #2}elseif(n===3){// Execute code block #3}else{// If all else fails, execute block #4}
if(n===1){// Execute code block #1}else{if(n===2){// Execute code block #2}else{if(n===3){// Execute code block #3}else{// If all else fails, execute block #4}}}
switch(expression) {statements}
switch(n){case1:// Start here if n === 1// Execute code block #1.break;// Stop herecase2:// Start here if n === 2// Execute code block #2.break;// Stop herecase3:// Start here if n === 3// Execute code block #3.break;// Stop heredefault:// If all else fails...// Execute code block #4.break;// Stop here}
functionconvert(x){switch(typeofx){case"number":// Convert the number to a hexadecimal integerreturnx.toString(16);case"string":// Return the string enclosed in quotesreturn'"'+x+'"';default:// Convert any other type in the usual wayreturnString(x);}}
while (expression)statement
letcount=0;while(count<10){console.log(count);count++;}
do
statement
while (expression);
functionprintArray(a){letlen=a.length,i=0;if(len===0){console.log("Empty Array");}else{do{console.log(a[i]);}while(++i<len);}}
for(initialize;test;increment)statement
initialize; while(test) {statementincrement; }
for(letcount=0;count<10;count++){console.log(count);}
leti,j,sum=0;for(i=0,j=10;i<10;i++,j--){sum+=i*j;}
functiontail(o){// Return the tail of linked list ofor(;o.next;o=o.next)/* empty */;// Traverse while o.next is truthyreturno;}
letdata=[1,2,3,4,5,6,7,8,9],sum=0;for(letelementofdata){sum+=element;}sum// => 45
leto={x:1,y:2,z:3};for(letelementofo){// Throws TypeError because o is not iterableconsole.log(element);}
leto={x:1,y:2,z:3};letkeys="";for(letkofObject.keys(o)){keys+=k;}keys// => "xyz"
letsum=0;for(letvofObject.values(o)){sum+=v;}sum// => 6
letpairs="";for(let[k,v]ofObject.entries(o)){pairs+=k+v;}pairs// => "x1y2z3"
letfrequency={};for(letletterof"mississippi"){if(frequency[letter]){frequency[letter]++;}else{frequency[letter]=1;}}frequency// => {m: 1, i: 4, s: 4, p: 2}
lettext="Na na na na na na na na Batman!";letwordSet=newSet(text.split(" "));letunique=[];for(letwordofwordSet){unique.push(word);}unique// => ["Na", "na", "Batman!"]
letm=newMap([[1,"one"]]);for(let[key,value]ofm){key// => 1value// => "one"}
// Read chunks from an asynchronously iterable stream and print them outasyncfunctionprintStream(stream){forawait(letchunkofstream){console.log(chunk);}}
for (variableinobject)statement
for(letpino){// Assign property names of o to variable pconsole.log(o[p]);// Print the value of each property}
leto={x:1,y:2,z:3};leta=[],i=0;for(a[i++]ino)/* empty */;
for(letiina)console.log(i);
identifier:statement
mainloop:while(token!==null){// Code omitted...continuemainloop;// Jump to the next iteration of the named loop// More code omitted...}
break;
for(leti=0;i<a.length;i++){if(a[i]===target)break;}
break labelname;
letmatrix=getData();// Get a 2D array of numbers from somewhere// Now sum all the numbers in the matrix.letsum=0,success=false;// Start with a labeled statement that we can break out of if errors occurcomputeSum:if(matrix){for(letx=0;x<matrix.length;x++){letrow=matrix[x];if(!row)breakcomputeSum;for(lety=0;y<row.length;y++){letcell=row[y];if(isNaN(cell))breakcomputeSum;sum+=cell;}}success=true;}// The break statements jump here. If we arrive here with success == false// then there was something wrong with the matrix we were given.// Otherwise, sum contains the sum of all cells of the matrix.
continue;
continue labelname;
In a while loop, the specified expression at the beginning of
the loop is tested again, and if it’s true, the loop body is executed
starting from the top.
In a do/while loop, execution skips to the bottom of the
loop, where the loop condition is tested again before restarting the
loop at the top.
In a for loop, the increment expression is evaluated, and the
test expression is tested again to determine if another iteration
should be done.
In a for/of or for/in loop, the loop starts over with the next
iterated value or next property name being assigned to the specified
variable.
for(leti=0;i<data.length;i++){if(!data[i])continue;// Can't proceed with undefined datatotal+=data[i];}
return expression;
functionsquare(x){returnx*x;}// A function that has a return statementsquare(2)// => 4
functiondisplayObject(o){// Return immediately if the argument is null or undefined.if(!o)return;// Rest of function goes here...}
// A generator function that yields a range of integersfunction*range(from,to){for(leti=from;i<=to;i++){yieldi;}}
throw expression;
functionfactorial(x){// If the input argument is invalid, throw an exception!if(x<0)thrownewError("x must not be negative");// Otherwise, compute a value and return normallyletf;for(f=1;x>1;f*=x,x--)/* empty */;returnf;}factorial(4)// => 24
try{// Normally, this code runs from the top of the block to the bottom// without problems. But it can sometimes throw an exception,// either directly, with a throw statement, or indirectly, by calling// a method that throws an exception.}catch(e){// The statements in this block are executed if, and only if, the try// block throws an exception. These statements can use the local variable// e to refer to the Error object or other value that was thrown.// This block may handle the exception somehow, may ignore the// exception by doing nothing, or may rethrow the exception with throw.}finally{// This block contains statements that are always executed, regardless of// what happens in the try block. They are executed whether the try// block terminates:// 1) normally, after reaching the bottom of the block// 2) because of a break, continue, or return statement// 3) with an exception that is handled by a catch clause above// 4) with an uncaught exception that is still propagating}
try{// Ask the user to enter a numberletn=Number(prompt("Please enter a positive integer",""));// Compute the factorial of the number, assuming the input is validletf=factorial(n);// Display the resultalert(n+"! = "+f);}catch(ex){// If the user's input was not valid, we end up herealert(ex);// Tell the user what the error is}
// Simulate for(initialize;test;increment) body;initialize; while(test) { try {body; } finally {increment; } }
with (object)statement
document.forms[0].address.value
with(document.forms[0]){// Access form elements directly here. For example:name.value="";address.value="";.value="";}
letf=document.forms[0];f.name.value="";f.address.value="";f..value="";
functionf(o){if(o===undefined)debugger;// Temporary line for debugging purposes...// The rest of the function goes here.}
It does not include any language keywords: the directive is just an expression statement that consists of a special string literal (in single or double quotes).
It can appear only at the start of a script or at the start of a function body, before any real statements have appeared.
The with statement is not allowed in strict mode.
In strict mode, all variables must be declared: a ReferenceError is
thrown if you assign a value to an identifier that is not a declared
variable, function, function parameter, catch clause parameter, or
property of the global object. (In non-strict mode, this implicitly
declares a global variable by adding a new property to the global
object.)
In strict mode, functions invoked as functions (rather than as
methods) have a this value of undefined. (In non-strict mode,
functions invoked as functions are always passed the global object as
their this value.) Also, in strict mode, when a function is invoked
with call() or apply() (§8.7.4), the this value is
exactly the value passed as the first argument to call() or
apply(). (In non-strict mode, null and undefined values are
replaced with the global object and nonobject values are converted to
objects.)
In strict mode, assignments to nonwritable properties and attempts to create new properties on non-extensible objects throw a TypeError. (In non-strict mode, these attempts fail silently.)
In strict mode, code passed to eval() cannot declare variables or
define functions in the caller’s scope as it can in non-strict mode.
Instead, variable and function definitions live in a new scope created
for the eval(). This scope is discarded when the eval() returns.
In strict mode, the Arguments object (§8.3.3) in a function holds a static copy of the values passed to the function. In non-strict mode, the Arguments object has “magical” behavior in which elements of the array and named function parameters both refer to the same value.
In strict mode, a SyntaxError is thrown if the delete operator is
followed by an unqualified identifier such as a variable, function, or
function parameter. (In nonstrict mode, such a delete expression
does nothing and evaluates to false.)
In strict mode, an attempt to delete a nonconfigurable property
throws a TypeError. (In non-strict mode, the attempt fails and the
delete expression evaluates to false.)
In strict mode, it is a syntax error for an object literal to define two or more properties by the same name. (In non-strict mode, no error occurs.)
In strict mode, it is a syntax error for a function declaration to have two or more parameters with the same name. (In non-strict mode, no error occurs.)
In strict mode, octal integer literals (beginning with a 0 that is not followed by an x) are not allowed. (In non-strict mode, some implementations allow octal literals.)
In strict mode, the identifiers eval and arguments are treated
like keywords, and you are not allowed to change their value. You
cannot assign a value to these identifiers, declare them as variables,
use them as function names, use them as function parameter names, or
use them as the identifier of a catch block.
In strict mode, the ability to examine the call stack is restricted.
arguments.caller and arguments.callee both throw a TypeError within
a strict mode function. Strict mode functions also have caller and
arguments properties that throw TypeError when read. (Some
implementations define these nonstandard properties on non-strict
functions.)
constTAU=2*Math.PI;letradius=3;varcircumference=TAU*radius;
functionarea(radius){returnMath.PI*radius*radius;}
classCircle{constructor(radius){this.r=radius;}area(){returnMath.PI*this.r*this.r;}circumference(){return2*Math.PI*this.r;}}
importCirclefrom'./geometry/circle.js';import{PI,TAU}from'./geometry/constants.js';import{magnitudeashypotenuse}from'./vectors/utils.js';
// geometry/constants.jsconstPI=Math.PI;constTAU=2*PI;export{PI,TAU};
exportconstTAU=2*Math.PI;exportfunctionmagnitude(x,y){returnMath.sqrt(x*x+y*y);}exportdefaultclassCircle{/* class definition omitted here */}
| Statement | Purpose |
|---|---|
break |
Exit from the innermost loop or |
case |
Label a statement within a |
class |
Declare a class |
const |
Declare and initialize one or more constants |
continue |
Begin next iteration of the innermost loop or the named loop |
debugger |
Debugger breakpoint |
default |
Label the default statement within a |
do/while |
An alternative to the |
export |
Declare values that can be imported into other modules |
for |
An easy-to-use loop |
for/await |
Asynchronously iterate the values of an async iterator |
for/in |
Enumerate the property names of an object |
for/of |
Enumerate the values of an iterable object such as an array |
function |
Declare a function |
if/else |
Execute one statement or another depending on a condition |
import |
Declare names for values defined in other modules |
label |
Give statement a name for use with |
let |
Declare and initialize one or more block-scoped variables (new syntax) |
return |
Return a value from a function |
switch |
Multiway branch to |
throw |
Throw an exception |
try/catch/finally |
Handle exceptions and code cleanup |
“use strict” |
Apply strict mode restrictions to script or function |
var |
Declare and initialize one or more variables (old syntax) |
while |
A basic loop construct |
with |
Extend the scope chain (deprecated and forbidden in strict mode) |
yield |
Provide a value to be iterated; only used in generator functions |
1 The fact that the case expressions are evaluated at runtime makes the JavaScript switch statement much different from (and less efficient than) the switch statement of C, C++, and Java. In those languages, the case expressions must be compile-time constants of the same type, and switch statements can often compile down to highly efficient jump tables.
2 When we consider the continue statement in §5.5.3, we’ll see that this while loop is not an exact equivalent of the for loop.
letempty={};// An object with no propertiesletpoint={x:0,y:0};// Two numeric propertiesletp2={x:point.x,y:point.y+1};// More complex valuesletbook={"main title":"JavaScript",// These property names include spaces,"sub-title":"The Definitive Guide",// and hyphens, so use string literals.for:"all audiences",// for is reserved, but no quotes.author:{// The value of this property isfirstname:"David",// itself an object.surname:"Flanagan"}};
leto=newObject();// Create an empty object: same as {}.leta=newArray();// Create an empty array: same as [].letd=newDate();// Create a Date object representing the current timeletr=newMap();// Create a Map object for key/value mapping
leto1=Object.create({x:1,y:2});// o1 inherits properties x and y.o1.x+o1.y// => 3
leto2=Object.create(null);// o2 inherits no props or methods.
leto3=Object.create(Object.prototype);// o3 is like {} or new Object().
leto={x:"don't change this value"};library.function(Object.create(o));// Guard against accidental modifications
letauthor=book.author;// Get the "author" property of the book.letname=author.surname;// Get the "surname" property of the author.lettitle=book["main title"];// Get the "main title" property of the book.
book.edition=7;// Create an "edition" property of book.book["main title"]="ECMAScript";// Change the "main title" property.
object.propertyobject["property"]
letaddr="";for(leti=0;i<4;i++){addr+=customer[`address${i}`]+"\n";}
functionaddstock(portfolio,stockname,shares){portfolio[stockname]=shares;}
functioncomputeValue(portfolio){lettotal=0.0;for(letstockinportfolio){// For each stock in the portfolio:letshares=portfolio[stock];// get the number of sharesletprice=getQuote(stock);// look up share pricetotal+=shares*price;// add stock value to total value}returntotal;// Return total value.}
leto={};// o inherits object methods from Object.prototypeo.x=1;// and it now has an own property x.letp=Object.create(o);// p inherits properties from o and Object.prototypep.y=2;// and has an own property y.letq=Object.create(p);// q inherits properties from p, o, and...q.z=3;// ...Object.prototype and has an own property z.letf=q.toString();// toString is inherited from Object.prototypeq.x+q.y// => 3; x and y are inherited from o and p
letunitcircle={r:1};// An object to inherit fromletc=Object.create(unitcircle);// c inherits the property rc.x=1;c.y=1;// c defines two properties of its ownc.r=2;// c overrides its inherited propertyunitcircle.r// => 1: the prototype is not affected
book.subtitle// => undefined: property doesn't exist
letlen=book.subtitle.length;// !TypeError: undefined doesn't have length
// A verbose and explicit techniqueletsurname=undefined;if(book){if(book.author){surname=book.author.surname;}}// A concise and idiomatic alternative to get surname or null or undefinedsurname=book&&book.author&&book.author.surname;
letsurname=book?.author?.surname;
o has an own property p that is read-only: it is not possible to
set read-only properties.
o has an inherited property p that is read-only: it is not
possible to hide an inherited read-only property with an own property
of the same name.
o does not have an own property p; o does not inherit a
property p with a setter method, and o’s extensible attribute
(see §14.2) is false. Since p does not already exist in
o, and if there is no setter method to call, then p must be added
to o. But if o is not extensible, then no new properties can be
defined on it.
deletebook.author;// The book object now has no author property.deletebook["main title"];// Now it doesn't have "main title", either.
leto={x:1};// o has own property x and inherits property toStringdeleteo.x// => true: deletes property xdeleteo.x// => true: does nothing (x doesn't exist) but true anywaydeleteo.toString// => true: does nothing (toString isn't an own property)delete1// => true: nonsense, but true anyway
// In strict mode, all these deletions throw TypeError instead of returning falsedeleteObject.prototype// => false: property is non-configurablevarx=1;// Declare a global variabledeleteglobalThis.x// => false: can't delete this propertyfunctionf(){}// Declare a global functiondeleteglobalThis.f// => false: can't delete this property either
globalThis.x=1;// Create a configurable global property (no let or var)deletex// => true: this property can be deleted
deletex;// SyntaxError in strict modedeleteglobalThis.x;// This works
leto={x:1};"x"ino// => true: o has an own property "x""y"ino// => false: o doesn't have a property "y""toString"ino// => true: o inherits a toString property
leto={x:1};o.hasOwnProperty("x")// => true: o has an own property xo.hasOwnProperty("y")// => false: o doesn't have a property yo.hasOwnProperty("toString")// => false: toString is an inherited property
leto={x:1};o.propertyIsEnumerable("x")// => true: o has an own enumerable property xo.propertyIsEnumerable("toString")// => false: not an own propertyObject.prototype.propertyIsEnumerable("toString")// => false: not enumerable
leto={x:1};o.x!==undefined// => true: o has a property xo.y!==undefined// => false: o doesn't have a property yo.toString!==undefined// => true: o inherits a toString property
leto={x:undefined};// Property is explicitly set to undefinedo.x!==undefined// => false: property exists but is undefinedo.y!==undefined// => false: property doesn't even exist"x"ino// => true: the property exists"y"ino// => false: the property doesn't existdeleteo.x;// Delete the property x"x"ino// => false: it doesn't exist anymore
leto={x:1,y:2,z:3};// Three enumerable own propertieso.propertyIsEnumerable("toString")// => false: not enumerablefor(letpino){// Loop through the propertiesconsole.log(p);// Prints x, y, and z, but not toString}
for(letpino){if(!o.hasOwnProperty(p))continue;// Skip inherited properties}for(letpino){if(typeofo[p]==="function")continue;// Skip all methods}
Object.keys() returns an array of the names of the enumerable own
properties of an object. It does not include non-enumerable
properties, inherited properties, or properties whose name is a Symbol
(see §6.10.3).
Object.getOwnPropertyNames() works like Object.keys() but
returns an array of the names of non-enumerable own properties as
well, as long as their names are strings.
Object.getOwnPropertySymbols() returns own properties whose names
are Symbols, whether or not they are enumerable.
Reflect.ownKeys() returns all own property names, both enumerable
and non-enumerable, and both string and Symbol. (See §14.6.)
String properties whose names are non-negative integers are listed first, in numeric order from smallest to largest. This rule means that arrays and array-like objects will have their properties enumerated in order.
After all properties that look like array indexes are listed, all remaining properties with string names are listed (including properties that look like negative numbers or floating-point numbers). These properties are listed in the order in which they were added to the object. For properties defined in an object literal, this order is the same order they appear in the literal.
Finally, the properties whose names are Symbol objects are listed in the order in which they were added to the object.
lettarget={x:1},source={y:2,z:3};for(letkeyofObject.keys(source)){target[key]=source[key];}target// => {x: 1, y: 2, z: 3}
Object.assign(o,defaults);// overwrites everything in o with defaults
o=Object.assign({},defaults,o);
o={...defaults,...o};
// Like Object.assign() but doesn't override existing properties// (and also doesn't handle Symbol properties)functionmerge(target,...sources){for(letsourceofsources){for(letkeyofObject.keys(source)){if(!(keyintarget)){// This is different than Object.assign()target[key]=source[key];}}}returntarget;}Object.assign({x:1},{x:2,y:2},{y:3,z:4})// => {x: 2, y: 3, z: 4}merge({x:1},{x:2,y:2},{y:3,z:4})// => {x: 1, y: 2, z: 4}
leto={x:1,y:{z:[false,null,""]}};// Define a test objectlets=JSON.stringify(o);// s == '{"x":1,"y":{"z":[false,null,""]}}'letp=JSON.parse(s);// p == {x: 1, y: {z: [false, null, ""]}}
lets={x:1,y:1}.toString();// s == "[object Object]"
letpoint={x:1,y:2,toString:function(){return`(${this.x},${this.y})`;}};String(point)// => "(1, 2)": toString() is used for string conversions
letpoint={x:1000,y:2000,toString:function(){return`(${this.x},${this.y})`;},toLocaleString:function(){return`(${this.x.toLocaleString()},${this.y.toLocaleString()})`;}};point.toString()// => "(1000, 2000)"point.toLocaleString()// => "(1,000, 2,000)": note thousands separators
letpoint={x:3,y:4,valueOf:function(){returnMath.hypot(this.x,this.y);}};Number(point)// => 5: valueOf() is used for conversions to numberspoint>4// => truepoint>5// => falsepoint<6// => true
letpoint={x:1,y:2,toString:function(){return`(${this.x},${this.y})`;},toJSON:function(){returnthis.toString();}};JSON.stringify([point])// => '["(1, 2)"]'
letx=1,y=2;leto={x:x,y:y};
letx=1,y=2;leto={x,y};o.x+o.y// => 3
constPROPERTY_NAME="p1";functioncomputePropertyName(){return"p"+2;}leto={};o[PROPERTY_NAME]=1;o[computePropertyName()]=2;
constPROPERTY_NAME="p1";functioncomputePropertyName(){return"p"+2;}letp={[PROPERTY_NAME]:1,[computePropertyName()]:2};p.p1+p.p2// => 3
constextension=Symbol("my extension symbol");leto={[extension]:{/* extension data stored in this object */}};o[extension].x=0;// This won't conflict with other properties of o
letposition={x:0,y:0};letdimensions={width:100,height:75};letrect={...position,...dimensions};rect.x+rect.y+rect.width+rect.height// => 175
leto={x:1};letp={x:0,...o};p.x// => 1: the value from object o overrides the initial valueletq={...o,x:2};q.x// => 2: the value 2 overrides the previous value from o.
leto=Object.create({x:1});// o inherits the property xletp={...o};p.x// => undefined
letsquare={area:function(){returnthis.side*this.side;},side:10};square.area()// => 100
letsquare={area(){returnthis.side*this.side;},side:10};square.area()// => 100
constMETHOD_NAME="m";constsymbol=Symbol();letweirdMethods={"method With Spaces"(x){returnx+1;},[METHOD_NAME](x){returnx+2;},[symbol](x){returnx+3;}};weirdMethods["method With Spaces"](1)// => 2weirdMethods[METHOD_NAME](1)// => 3weirdMethods[symbol](1)// => 4
leto={// An ordinary data propertydataProp:value,// An accessor property defined as a pair of functions.getaccessorProp(){returnthis.dataProp;},setaccessorProp(value){this.dataProp=value;}};
letp={// x and y are regular read-write data properties.x:1.0,y:1.0,// r is a read-write accessor property with getter and setter.// Don't forget to put a comma after accessor methods.getr(){returnMath.hypot(this.x,this.y);},setr(newvalue){letoldvalue=Math.hypot(this.x,this.y);letratio=newvalue/oldvalue;this.x*=ratio;this.y*=ratio;},// theta is a read-only accessor property with getter only.gettheta(){returnMath.atan2(this.y,this.x);}};p.r// => Math.SQRT2p.theta// => Math.PI / 4
letq=Object.create(p);// A new object that inherits getters and settersq.x=3;q.y=4;// Create q's own data propertiesq.r// => 5: the inherited accessor properties workq.theta// => Math.atan2(4, 3)
// This object generates strictly increasing serial numbersconstserialnum={// This data property holds the next serial number.// The _ in the property name hints that it is for internal use only._n:0,// Return the current value and increment itgetnext(){returnthis._n++;},// Set a new value of n, but only if it is larger than currentsetnext(n){if(n>this._n)this._n=n;elsethrownewError("serial number can only be set to a larger value");}};serialnum.next=10;// Set the starting serial numberserialnum.next// => 10serialnum.next// => 11: different value each time we get next
// This object has accessor properties that return random numbers.// The expression "random.octet", for example, yields a random number// between 0 and 255 each time it is evaluated.constrandom={getoctet(){returnMath.floor(Math.random()*256);},getuint16(){returnMath.floor(Math.random()*65536);},getint16(){returnMath.floor(Math.random()*65536)-32768;}};
Basic object terminology, including the meaning of terms like enumerable and own property.
Object literal syntax, including the many new features in ES6 and later.
How to read, write, delete, enumerate, and check for the presence of the properties of an object.
How prototype-based inheritance works in JavaScript and how to
create an object that inherits from another object with
Object.create().
How to copy properties from one object into another with
Object.assign().
Array literals
The ... spread operator on an iterable object
The Array() constructor
The Array.of() and Array.from() factory methods
letempty=[];// An array with no elementsletprimes=[2,3,5,7,11];// An array with 5 numeric elementsletmisc=[1.1,true,"a",];// 3 elements of various types + trailing comma
letbase=1024;lettable=[base,base+1,base+2,base+3];
letb=[[1,{x:1,y:2}],[2,{x:3,y:4}]];
letcount=[1,,3];// Elements at indexes 0 and 2. No element at index 1letundefs=[,,];// An array with no elements but a length of 2
leta=[1,2,3];letb=[0,...a,4];// b == [0, 1, 2, 3, 4]
letoriginal=[1,2,3];letcopy=[...original];copy[0]=0;// Modifying the copy does not change the originaloriginal[0]// => 1
letdigits=[..."0123456789ABCDEF"];digits// => ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"]
letletters=[..."hello world"];[...newSet(letters)]// => ["h","e","l","o"," ","w","r","d"]
Call it with no arguments:
leta=newArray();
This method creates an empty array with no elements and is equivalent
to the array literal [].
Call it with a single numeric argument, which specifies a length:
leta=newArray(10);
This technique creates an array with the specified length. This form
of the Array() constructor can be used to preallocate an array
when you know in advance how many elements will be required. Note that
no values are stored in the array, and the array index properties
“0”, “1”, and so on are not even defined for
the array.
Explicitly specify two or more array elements or a single non-numeric element for the array:
leta=newArray(5,4,3,2,1,"testing, testing");
In this form, the constructor arguments become the elements of the new
array. Using an array literal is almost always simpler than this usage
of the Array() constructor.
Array.of()// => []; returns empty array with no argumentsArray.of(10)// => [10]; can create arrays with a single numeric argumentArray.of(1,2,3)// => [1, 2, 3]
letcopy=Array.from(original);
lettruearray=Array.from(arraylike);
leta=["world"];// Start with a one-element arrayletvalue=a[0];// Read element 0a[1]=3.14;// Write element 1leti=2;a[i]=3;// Write element 2a[i+1]="hello";// Write element 3a[a[i]]=a[0];// Read elements 0 and 2, write element 3
a.length// => 4
leto={};// Create a plain objecto[1]="one";// Index it with an integero["1"]// => "one"; numeric and string property names are the same
a[-1.23]=true;// This creates a property named "-1.23"a["1000"]=0;// This the 1001st element of the arraya[1.000]=1;// Array index 1. Same as a[1] = 1;
leta=[true,false];// This array has elements at indexes 0 and 1a[2]// => undefined; no element at this index.a[-1]// => undefined; no property with this name.
leta=newArray(5);// No elements, but a.length is 5.a=[];// Create an array with no elements and length = 0.a[1000]=0;// Assignment adds one element but sets length to 1001.
leta1=[,];// This array has no elements and length 1leta2=[undefined];// This array has one undefined element0ina1// => false: a1 has no element with index 00ina2// => true: a2 has the undefined value at index 0
[].length// => 0: the array has no elements["a","b","c"].length// => 3: highest index is 2, length is 3
a=[1,2,3,4,5];// Start with a 5-element array.a.length=3;// a is now [1,2,3].a.length=0;// Delete all elements. a is [].a.length=5;// Length is 5, but no elements, like new Array(5)
leta=[];// Start with an empty array.a[0]="zero";// And add elements to it.a[1]="one";
leta=[];// Start with an empty arraya.push("zero");// Add a value at the end. a = ["zero"]a.push("one","two");// Add two more values. a = ["zero", "one", "two"]
leta=[1,2,3];deletea[2];// a now has no element at index 22ina// => false: no array index 2 is defineda.length// => 3: delete does not affect array length
letletters=[..."Hello world"];// An array of lettersletstring="";for(letletterofletters){string+=letter;}string// => "Hello world"; we reassembled the original text
leteveryother="";for(let[index,letter]ofletters.entries()){if(index%2===0)everyother+=letter;// letters at even indexes}everyother// => "Hlowrd"
letuppercase="";letters.forEach(letter=>{// Note arrow function syntax hereuppercase+=letter.toUpperCase();});uppercase// => "HELLO WORLD"
letvowels="";for(leti=0;i<letters.length;i++){// For each index in the arrayletletter=letters[i];// Get the element at that indexif(/[aeiou]/.test(letter)){// Use a regular expression testvowels+=letter;// If it is a vowel, remember it}}vowels// => "eoo"
// Save the array length into a local variablefor(leti=0,len=letters.length;i<len;i++){// loop body remains the same}// Iterate backwards from the end of the array to the startfor(leti=letters.length-1;i>=0;i--){// loop body remains the same}
for(leti=0;i<a.length;i++){if(a[i]===undefined)continue;// Skip undefined + nonexistent elements// loop body here}
// Create a multidimensional arraylettable=newArray(10);// 10 rows of the tablefor(leti=0;i<table.length;i++){table[i]=newArray(10);// Each row has 10 columns}// Initialize the arrayfor(letrow=0;row<table.length;row++){for(letcol=0;col<table[row].length;col++){table[row][col]=row*col;}}// Use the multidimensional array to compute 5*7table[5][7]// => 35
Iterator methods loop over the elements of an array, typically invoking a function that you specify on each of those elements.
Stack and queue methods add and remove array elements to and from the beginning and the end of an array.
Subarray methods are for extracting, deleting, inserting, filling, and copying contiguous regions of a larger array.
Searching and sorting methods are for locating elements within an array and for sorting the elements of an array.
letdata=[1,2,3,4,5],sum=0;// Compute the sum of the elements of the arraydata.forEach(value=>{sum+=value;});// sum == 15// Now increment each array elementdata.forEach(function(v,i,a){a[i]=v+1;});// data == [2,3,4,5,6]
leta=[1,2,3];a.map(x=>x*x)// => [1, 4, 9]: the function takes input x and returns x*x
leta=[5,4,3,2,1];a.filter(x=>x<3)// => [2, 1]; values less than 3a.filter((x,i)=>i%2===0)// => [5, 3, 1]; every other value
letdense=sparse.filter(()=>true);
a=a.filter(x=>x!==undefined&&x!==null);
leta=[1,2,3,4,5];a.findIndex(x=>x===3)// => 2; the value 3 appears at index 2a.findIndex(x=>x<0)// => -1; no negative numbers in the arraya.find(x=>x%5===0)// => 5: this is a multiple of 5a.find(x=>x%7===0)// => undefined: no multiples of 7 in the array
leta=[1,2,3,4,5];a.every(x=>x<10)// => true: all values are < 10.a.every(x=>x%2===0)// => false: not all values are even.
leta=[1,2,3,4,5];a.some(x=>x%2===0)// => true; a has some even numbers.a.some(isNaN)// => false; a has no non-numbers.
leta=[1,2,3,4,5];a.reduce((x,y)=>x+y,0)// => 15; the sum of the valuesa.reduce((x,y)=>x*y,1)// => 120; the product of the valuesa.reduce((x,y)=>(x>y)?x:y)// => 5; the largest of the values
// Compute 2^(3^4). Exponentiation has right-to-left precedenceleta=[2,3,4];a.reduceRight((acc,val)=>Math.pow(val,acc))// => 2.4178516392292583e+24
[1,[2,3]].flat()// => [1, 2, 3][1,[2,[3]]].flat()// => [1, 2, [3]]
leta=[1,[2,[3,[4]]]];a.flat(1)// => [1, 2, [3, [4]]]a.flat(2)// => [1, 2, 3, [4]]a.flat(3)// => [1, 2, 3, 4]a.flat(4)// => [1, 2, 3, 4]
letphrases=["hello world","the definitive guide"];letwords=phrases.flatMap(phrase=>phrase.split(" "));words// => ["hello", "world", "the", "definitive", "guide"];
// Map non-negative numbers to their square roots[-2,-1,1,2].flatMap(x=>x<0?[]:Math.sqrt(x))// => [1, 2**0.5]
leta=[1,2,3];a.concat(4,5)// => [1,2,3,4,5]a.concat([4,5],[6,7])// => [1,2,3,4,5,6,7]; arrays are flatteneda.concat(4,[5,[6,7]])// => [1,2,3,4,5,[6,7]]; but not nested arraysa// => [1,2,3]; the original array is unmodified
letstack=[];// stack == []stack.push(1,2);// stack == [1,2];stack.pop();// stack == [1]; returns 2stack.push(3);// stack == [1,3]stack.pop();// stack == [1]; returns 3stack.push([4,5]);// stack == [1,[4,5]]stack.pop()// stack == [1]; returns [4,5]stack.pop();// stack == []; returns 1
a.push(...values);
letq=[];// q == []q.push(1,2);// q == [1,2]q.shift();// q == [2]; returns 1q.push(3)// q == [2, 3]q.shift()// q == [3]; returns 2q.shift()// q == []; returns 3
leta=[];// a == []a.unshift(1)// a == [1]a.unshift(2)// a == [2, 1]a=[];// a == []a.unshift(1,2)// a == [1, 2]
leta=[1,2,3,4,5];a.slice(0,3);// Returns [1,2,3]a.slice(3);// Returns [4,5]a.slice(1,-1);// Returns [2,3,4]a.slice(-3,-2);// Returns [3]
leta=[1,2,3,4,5,6,7,8];a.splice(4)// => [5,6,7,8]; a is now [1,2,3,4]a.splice(1,2)// => [2,3]; a is now [1,4]a.splice(1,1)// => [4]; a is now [1]
leta=[1,2,3,4,5];a.splice(2,0,"a","b")// => []; a is now [1,2,"a","b",3,4,5]a.splice(2,2,[1,2],3)// => ["a","b"]; a is now [1,2,[1,2],3,3,4,5]
leta=newArray(5);// Start with no elements and length 5a.fill(0)// => [0,0,0,0,0]; fill the array with zerosa.fill(9,1)// => [0,9,9,9,9]; fill with 9 starting at index 1a.fill(8,2,-1)// => [0,9,8,8,9]; fill with 8 at indexes 2, 3
leta=[1,2,3,4,5];a.copyWithin(1)// => [1,1,2,3,4]: copy array elements up onea.copyWithin(2,3,5)// => [1,1,3,4,4]: copy last 2 elements to index 2a.copyWithin(0,-2)// => [4,4,3,4,4]: negative offsets work, too
leta=[0,1,2,1,0];a.indexOf(1)// => 1: a[1] is 1a.lastIndexOf(1)// => 3: a[3] is 1a.indexOf(3)// => -1: no element has value 3
// Find all occurrences of a value x in an array a and return an array// of matching indexesfunctionfindall(a,x){letresults=[],// The array of indexes we'll returnlen=a.length,// The length of the array to be searchedpos=0;// The position to search fromwhile(pos<len){// While more elements to search...pos=a.indexOf(x,pos);// Searchif(pos===-1)break;// If nothing found, we're done.results.push(pos);// Otherwise, store index in arraypos=pos+1;// And start next search at next element}returnresults;// Return array of indexes}
leta=[1,true,3,NaN];a.includes(true)// => truea.includes(2)// => falsea.includes(NaN)// => truea.indexOf(NaN)// => -1; indexOf can't find NaN
leta=["banana","cherry","apple"];a.sort();// a == ["apple", "banana", "cherry"]
leta=[33,4,1111,222];a.sort();// a == [1111, 222, 33, 4]; alphabetical ordera.sort(function(a,b){// Pass a comparator functionreturna-b;// Returns < 0, 0, or > 0, depending on order});// a == [4, 33, 222, 1111]; numerical ordera.sort((a,b)=>b-a);// a == [1111, 222, 33, 4]; reverse numerical order
leta=["ant","Bug","cat","Dog"];a.sort();// a == ["Bug","Dog","ant","cat"]; case-sensitive sorta.sort(function(s,t){leta=s.toLowerCase();letb=t.toLowerCase();if(a<b)return-1;if(a>b)return1;return0;});// a == ["ant","Bug","cat","Dog"]; case-insensitive sort
leta=[1,2,3];a.reverse();// a == [3,2,1]
leta=[1,2,3];a.join()// => "1,2,3"a.join(" ")// => "1 2 3"a.join("")// => "123"letb=newArray(10);// An array of length 10 with no elementsb.join("-")// => "---------": a string of 9 hyphens
[1,2,3].toString()// => "1,2,3"["a","b","c"].toString()// => "a,b,c"[1,[2,"c"]].toString()// => "1,2,c"
Array.isArray([])// => trueArray.isArray({})// => false
The length property is automatically updated as new elements are added to
the list.
Setting length to a smaller value truncates the array.
Array.isArray() returns true for arrays.
leta={};// Start with a regular empty object// Add properties to make it "array-like"leti=0;while(i<10){a[i]=i*i;i++;}a.length=i;// Now iterate through it as if it were a real arraylettotal=0;for(letj=0;j<a.length;j++){total+=a[j];}
// Determine if o is an array-like object.// Strings and functions have numeric length properties, but are// excluded by the typeof test. In client-side JavaScript, DOM text// nodes have a numeric length property, and may need to be excluded// with an additional o.nodeType !== 3 test.functionisArrayLike(o){if(o&&// o is not null, undefined, etc.typeofo==="object"&&// o is an objectNumber.isFinite(o.length)&&// o.length is a finite numbero.length>=0&&// o.length is non-negativeNumber.isInteger(o.length)&&// o.length is an integero.length<4294967295){// o.length < 2^32 - 1returntrue;// Then o is array-like.}else{returnfalse;// Otherwise it is not.}}
leta={"0":"a","1":"b","2":"c",length:3};// An array-like objectArray.prototype.join.call(a,"+")// => "a+b+c"Array.prototype.map.call(a,x=>x.toUpperCase())// => ["A","B","C"]Array.prototype.slice.call(a,0)// => ["a","b","c"]: true array copyArray.from(a)// => ["a","b","c"]: easier array copy
lets="test";s.charAt(0)// => "t"s[1]// => "e"
Array.prototype.join.call("JavaScript"," ")// => "J a v a S c r i p t"
Array literals are written as comma-separated lists of values within square brackets.
Individual array elements are accessed by specifying the desired array index within square brackets.
The for/of loop and ... spread operator introduced in ES6 are
particularly useful ways to iterate arrays.
The Array class defines a rich set of methods for manipulating arrays, and you should be sure to familiarize yourself with the Array API.
An identifier that names the function. The name is a required part of function declarations: it is used as the name of a variable, and the newly defined function object is assigned to the variable.
A pair of parentheses around a comma-separated list of zero or more identifiers. These identifiers are the parameter names for the function, and they behave like local variables within the body of the function.
A pair of curly braces with zero or more JavaScript statements inside. These statements are the body of the function: they are executed whenever the function is invoked.
// Print the name and value of each property of o. Return undefined.functionprintprops(o){for(letpino){console.log(`${p}:${o[p]}\n`);}}// Compute the distance between Cartesian points (x1,y1) and (x2,y2).functiondistance(x1,y1,x2,y2){letdx=x2-x1;letdy=y2-y1;returnMath.sqrt(dx*dx+dy*dy);}// A recursive function (one that calls itself) that computes factorials// Recall that x! is the product of x and all positive integers less than it.functionfactorial(x){if(x<=1)return1;returnx*factorial(x-1);}
// This function expression defines a function that squares its argument.// Note that we assign it to a variableconstsquare=function(x){returnx*x;};// Function expressions can include names, which is useful for recursion.constf=functionfact(x){if(x<=1)return1;elsereturnx*fact(x-1);};// Function expressions can also be used as arguments to other functions:[3,2,1].sort(function(a,b){returna-b;});// Function expressions are sometimes defined and immediately invoked:lettensquared=(function(x){returnx*x;}(10));
constsum=(x,y)=>{returnx+y;};
constsum=(x,y)=>x+y;
constpolynomial=x=>x*x+2*x+3;
constconstantFunc=()=>42;
constf=x=>{return{value:x};};// Good: f() returns an objectconstg=x=>({value:x});// Good: g() returns an objectconsth=x=>{value:x};// Bad: h() returns nothingconsti=x=>{v:x,w:x};// Bad: Syntax Error
// Make a copy of an array with null elements removed.letfiltered=[1,null,2,3].filter(x=>x!==null);// filtered == [1,2,3]// Square some numbers:letsquares=[1,2,3,4].map(x=>x*x);// squares == [1,4,9,16]
functionhypotenuse(a,b){functionsquare(x){returnx*x;}returnMath.sqrt(square(a)+square(b));}
As functions
As methods
As constructors
Indirectly through their call() and apply() methods
Implicitly, via JavaScript language features that do not appear like normal function invocations
printprops({x:1});lettotal=distance(0,0,2,1)+distance(2,1,3,5);letprobability=factorial(5)/factorial(13);
// Define and invoke a function to determine if we're in strict mode.conststrict=(function(){return!this;}());
o.m=f;
o.m();
o.m(x,y);
letcalculator={// An object literaloperand1:1,operand2:1,add(){// We're using method shorthand syntax for this function// Note the use of the this keyword to refer to the containing object.this.result=this.operand1+this.operand2;}};calculator.add();// A method invocation to compute 1+1.calculator.result// => 2
o["m"](x,y);// Another way to write o.m(x,y).a[0](z)// Also a method invocation (assuming a[0] is a function).
customer.surname.toUpperCase();// Invoke method on customer.surnamef().m();// Invoke method m() on return value of f()
rect.setSize(width,height);setRectSize(rect,width,height);
leto={// An object o.m:function(){// Method m of the object.letself=this;// Save the "this" value in a variable.this===o// => true: "this" is the object o.f();// Now call the helper function f().functionf(){// A nested function fthis===o// => false: "this" is global or undefinedself===o// => true: self is the outer "this" value.}}};o.m();// Invoke the method m on the object o.
constf=()=>{this===o// true, since arrow functions inherit this};
constf=(function(){this===o// true, since we bound this function to the outer this}).bind(this);
o=newObject();o=newObject;
If an object has getters or setters defined, then querying or setting the value of its properties may invoke those methods. See §6.10.6 for more information.
When an object is used in a string context (such as when it is
concatenated with a string), its toString() method is
called. Similarly, when an object is used in a numeric context, its
valueOf() method is invoked. See §3.9.3 for details.
When you loop over the elements of an iterable object, there are a number of method calls that occur. Chapter 12 explains how iterators work at the function call level and demonstrates how to write these methods so that you can define your own iterable types.
A tagged template literal is a function invocation in disguise. §14.5 demonstrates how to write functions that can be used in conjunction with template literal strings.
Proxy objects (described in §14.7) have their behavior completely controlled by functions. Just about any operation on one of these objects will cause a function to be invoked.
// Append the names of the enumerable properties of object o to the// array a, and return a. If a is omitted, create and return a new array.functiongetPropertyNames(o,a){if(a===undefined)a=[];// If undefined, use a new arrayfor(letpropertyino)a.push(property);returna;}// getPropertyNames() can be invoked with one or two arguments:leto={x:1},p={y:2,z:3};// Two objects for testingleta=getPropertyNames(o);// a == ["x"]; get o's properties in a new arraygetPropertyNames(p,a);// a == ["x","y","z"]; add p's properties to it
a=a||[];
// Append the names of the enumerable properties of object o to the// array a, and return a. If a is omitted, create and return a new array.functiongetPropertyNames(o,a=[]){for(letpropertyino)a.push(property);returna;}
// This function returns an object representing a rectangle's dimensions.// If only width is supplied, make it twice as high as it is wide.constrectangle=(width,height=width*2)=>({width,height});rectangle(1)// => { width: 1, height: 2 }
functionmax(first=-Infinity,...rest){letmaxValue=first;// Start by assuming the first arg is biggest// Then loop through the rest of the arguments, looking for biggerfor(letnofrest){if(n>maxValue){maxValue=n;}}// Return the biggestreturnmaxValue;}max(1,10,100,2,3,1000,4,5,6)// => 1000
functionmax(x){letmaxValue=-Infinity;// Loop through the arguments, looking for, and remembering, the biggest.for(leti=0;i<arguments.length;i++){if(arguments[i]>maxValue)maxValue=arguments[i];}// Return the biggestreturnmaxValue;}max(1,10,100,2,3,1000,4,5,6)// => 1000
letnumbers=[5,2,10,-1,9,100,1];Math.min(...numbers)// => -1
// This function takes a function and returns a wrapped versionfunctiontimed(f){returnfunction(...args){// Collect args into a rest parameter arrayconsole.log(`Entering function${f.name}`);letstartTime=Date.now();try{// Pass all of our arguments to the wrapped functionreturnf(...args);// Spread the args back out again}finally{// Before we return the wrapped return value, print elapsed time.console.log(`Exiting${f.name}after${Date.now()-startTime}ms`);}};}// Compute the sum of the numbers between 1 and n by brute forcefunctionbenchmark(n){letsum=0;for(leti=1;i<=n;i++)sum+=i;returnsum;}// Now invoke the timed version of that test functiontimed(benchmark)(1000000)// => 500000500000; this is the sum of the numbers
functionvectorAdd(v1,v2){return[v1[0]+v2[0],v1[1]+v2[1]];}vectorAdd([1,2],[3,4])// => [4,6]
functionvectorAdd([x1,y1],[x2,y2]){// Unpack 2 arguments into 4 parametersreturn[x1+x2,y1+y2];}vectorAdd([1,2],[3,4])// => [4,6]
// Multiply the vector {x,y} by a scalar valuefunctionvectorMultiply({x,y},scalar){return{x:x*scalar,y:y*scalar};}vectorMultiply({x:1,y:2},2)// => {x: 2, y: 4}
functionvectorAdd({x:x1,y:y1},// Unpack 1st object into x1 and y1 params{x:x2,y:y2}// Unpack 2nd object into x2 and y2 params){return{x:x1+x2,y:y1+y2};}vectorAdd({x:1,y:2},{x:3,y:4})// => {x: 4, y: 6}
// Multiply the vector {x,y} or {x,y,z} by a scalar valuefunctionvectorMultiply({x,y,z=0},scalar){return{x:x*scalar,y:y*scalar,z:z*scalar};}vectorMultiply({x:1,y:2},2)// => {x: 2, y: 4, z: 0}
functionarraycopy({from,to=from,n=from.length,fromIndex=0,toIndex=0}){letvaluesToCopy=from.slice(fromIndex,fromIndex+n);to.splice(toIndex,0,...valuesToCopy);returnto;}leta=[1,2,3,4,5],b=[9,8,7,6,5];arraycopy({from:a,n:3,to:b,toIndex:4})// => [9,8,7,6,1,2,3,5]
// This function expects an array argument. The first two elements of that// array are unpacked into the x and y parameters. Any remaining elements// are stored in the coords array. And any arguments after the first array// are packed into the rest array.functionf([x,y,...coords],...rest){return[x+y,...rest,...coords];// Note: spread operator here}f([1,2,3,4],5,6)// => [3, 5, 6, 3, 4]
// Multiply the vector {x,y} or {x,y,z} by a scalar value, retain other propsfunctionvectorMultiply({x,y,z=0,...props},scalar){return{x:x*scalar,y:y*scalar,z:z*scalar,...props};}vectorMultiply({x:1,y:2,w:-1},2)// => {x: 2, y: 4, z: 0, w: -1}
functiondrawCircle({x,y,radius,color:[r,g,b]}){// Not yet implemented}
// Return the sum of the elements an iterable object a.// The elements of a must all be numbers.functionsum(a){lettotal=0;for(letelementofa){// Throws TypeError if a is not iterableif(typeofelement!=="number"){thrownewTypeError("sum(): elements must be numbers");}total+=element;}returntotal;}sum([1,2,3])// => 6sum(1,2,3);// !TypeError: 1 is not iterablesum([1,2,"3"]);// !TypeError: element 2 is not a number
functionsquare(x){returnx*x;}
lets=square;// Now s refers to the same function that square doessquare(4)// => 16s(4)// => 16
leto={square:function(x){returnx*x;}};// An object literallety=o.square(16);// y == 256
leta=[x=>x*x,20];// An array literala[0](a[1])// => 400
// We define some simple functions herefunctionadd(x,y){returnx+y;}functionsubtract(x,y){returnx-y;}functionmultiply(x,y){returnx*y;}functiondivide(x,y){returnx/y;}// Here's a function that takes one of the preceding functions// as an argument and invokes it on two operandsfunctionoperate(operator,operand1,operand2){returnoperator(operand1,operand2);}// We could invoke this function like this to compute the value (2+3) + (4*5):leti=operate(add,operate(add,2,3),operate(multiply,4,5));// For the sake of the example, we implement the simple functions again,// this time within an object literal;constoperators={add:(x,y)=>x+y,subtract:(x,y)=>x-y,multiply:(x,y)=>x*y,divide:(x,y)=>x/y,pow:Math.pow// This works for predefined functions too};// This function takes the name of an operator, looks up that operator// in the object, and then invokes it on the supplied operands. Note// the syntax used to invoke the operator function.functionoperate2(operation,operand1,operand2){if(typeofoperators[operation]==="function"){returnoperators[operation](operand1,operand2);}elsethrow"unknown operator";}operate2("add","hello",operate2("add"," ","world"))// => "hello world"operate2("pow",10,2)// => 100
// Initialize the counter property of the function object.// Function declarations are hoisted so we really can// do this assignment before the function declaration.uniqueInteger.counter=0;// This function returns a different integer each time it is called.// It uses a property of itself to remember the next value to be returned.functionuniqueInteger(){returnuniqueInteger.counter++;// Return and increment counter property}uniqueInteger()// => 0uniqueInteger()// => 1
// Compute factorials and cache results as properties of the function itself.functionfactorial(n){if(Number.isInteger(n)&&n>0){// Positive integers onlyif(!(ninfactorial)){// If no cached resultfactorial[n]=n*factorial(n-1);// Compute and cache it}returnfactorial[n];// Return the cached result}else{returnNaN;// If input was bad}}factorial[1]=1;// Initialize the cache to hold this base case.factorial(6)// => 720factorial[5]// => 120; the call above caches this value
functionchunkNamespace(){// Chunk of code goes here// Any variables defined in the chunk are local to this function// instead of cluttering up the global namespace.}chunkNamespace();// But don't forget to invoke the function!
(function(){// chunkNamespace() function rewritten as an unnamed expression.// Chunk of code goes here}());// End the function literal and invoke it now.
letscope="global scope";// A global variablefunctioncheckscope(){letscope="local scope";// A local variablefunctionf(){returnscope;}// Return the value in scope herereturnf();}checkscope()// => "local scope"
letscope="global scope";// A global variablefunctioncheckscope(){letscope="local scope";// A local variablefunctionf(){returnscope;}// Return the value in scope herereturnf;}lets=checkscope()();// What does this return?
letuniqueInteger=(function(){// Define and invokeletcounter=0;// Private state of function belowreturnfunction(){returncounter++;};}());uniqueInteger()// => 0uniqueInteger()// => 1
functioncounter(){letn=0;return{count:function(){returnn++;},reset:function(){n=0;}};}letc=counter(),d=counter();// Create two countersc.count()// => 0d.count()// => 0: they count independentlyc.reset();// reset() and count() methods share statec.count()// => 0: because we reset cd.count()// => 1: d was not reset
functioncounter(n){// Function argument n is the private variablereturn{// Property getter method returns and increments private counter var.getcount(){returnn++;},// Property setter doesn't allow the value of n to decreasesetcount(m){if(m>n)n=m;elsethrowError("count can only be set to a larger value");}};}letc=counter(1000);c.count// => 1000c.count// => 1001c.count=2000;c.count// => 2000c.count=2000;// !Error: count can only be set to a larger value
// This function adds property accessor methods for a property with// the specified name to the object o. The methods are named get<name>// and set<name>. If a predicate function is supplied, the setter// method uses it to test its argument for validity before storing it.// If the predicate returns false, the setter method throws an exception.//// The unusual thing about this function is that the property value// that is manipulated by the getter and setter methods is not stored in// the object o. Instead, the value is stored only in a local variable// in this function. The getter and setter methods are also defined// locally to this function and therefore have access to this local variable.// This means that the value is private to the two accessor methods, and it// cannot be set or modified except through the setter method.functionaddPrivateProperty(o,name,predicate){letvalue;// This is the property value// The getter method simply returns the value.o[`get${name}`]=function(){returnvalue;};// The setter method stores the value or throws an exception if// the predicate rejects the value.o[`set${name}`]=function(v){if(predicate&&!predicate(v)){thrownewTypeError(`set${name}: invalid value${v}`);}else{value=v;}};}// The following code demonstrates the addPrivateProperty() method.leto={};// Here is an empty object// Add property accessor methods getName and setName()// Ensure that only string values are allowedaddPrivateProperty(o,"Name",x=>typeofx==="string");o.setName("Frank");// Set the property valueo.getName()// => "Frank"o.setName(0);// !TypeError: try to set a value of the wrong type
// This function returns a function that always returns vfunctionconstfunc(v){return()=>v;}// Create an array of constant functions:letfuncs=[];for(vari=0;i<10;i++)funcs[i]=constfunc(i);// The function at array element 5 returns the value 5.funcs[5]()// => 5
// Return an array of functions that return the values 0-9functionconstfuncs(){letfuncs=[];for(vari=0;i<10;i++){funcs[i]=()=>i;}returnfuncs;}letfuncs=constfuncs();funcs[5]()// => 10; Why doesn't this return 5?
constself=this;// Make the this value available to nested functions
f.call(o);f.apply(o);
o.m=f;// Make f a temporary method of o.o.m();// Invoke it, passing no arguments.deleteo.m;// Remove the temporary method.
f.call(o,1,2);
f.apply(o,[1,2]);
letbiggest=Math.max.apply(Math,arrayOfNumbers);
// Replace the method named m of the object o with a version that logs// messages before and after invoking the original method.functiontrace(o,m){letoriginal=o[m];// Remember original method in the closure.o[m]=function(...args){// Now define the new method.console.log(newDate(),"Entering:",m);// Log message.letresult=original.apply(this,args);// Invoke original.console.log(newDate(),"Exiting:",m);// Log message.returnresult;// Return result.};}
functionf(y){returnthis.x+y;}// This function needs to be boundleto={x:1};// An object we'll bind toletg=f.bind(o);// Calling g(x) invokes f() on og(2)// => 3letp={x:10,g};// Invoke g() as a method of this objectp.g(2)// => 3: g is still bound to o, not p.
letsum=(x,y)=>x+y;// Return the sum of 2 argsletsucc=sum.bind(null,1);// Bind the first argument to 1succ(2)// => 3: x is bound to 1, and we pass 2 for the y argumentfunctionf(y,z){returnthis.x+y+z;}letg=f.bind({x:1},2);// Bind this and yg(3)// => 6: this.x is bound to 1, y is bound to 2 and z is 3
constf=newFunction("x","y","return x*y;");
constf=function(x,y){returnx*y;};
The Function() constructor allows JavaScript functions to be
dynamically created and compiled at runtime.
The Function() constructor parses the function body and creates a
new function object each time it is called. If the call to the
constructor appears within a loop or within a frequently called
function, this process can be inefficient. By contrast, nested
functions and function expressions that appear within
loops are not recompiled each time they are encountered.
A last, very important point about the Function() constructor is
that the functions it creates do not use lexical scoping; instead,
they are always compiled as if they were top-level functions, as the
following code demonstrates:
letscope="global";functionconstructFunction(){letscope="local";returnnewFunction("return scope");// Doesn't capture local scope!}// This line returns "global" because the function returned by the// Function() constructor does not use the local scope.constructFunction()()// => "global"
letdata=[1,1,3,5,5];// This is our array of numbers// The mean is the sum of the elements divided by the number of elementslettotal=0;for(leti=0;i<data.length;i++)total+=data[i];letmean=total/data.length;// mean == 3; The mean of our data is 3// To compute the standard deviation, we first sum the squares of// the deviation of each element from the mean.total=0;for(leti=0;i<data.length;i++){letdeviation=data[i]-mean;total+=deviation*deviation;}letstddev=Math.sqrt(total/(data.length-1));// stddev == 2
// First, define two simple functionsconstsum=(x,y)=>x+y;constsquare=x=>x*x;// Then use those functions with Array methods to compute mean and stddevletdata=[1,1,3,5,5];letmean=data.reduce(sum)/data.length;// mean == 3letdeviations=data.map(x=>x-mean);letstddev=Math.sqrt(deviations.map(square).reduce(sum)/(data.length-1));stddev// => 2
constmap=function(a,...args){returna.map(...args);};constreduce=function(a,...args){returna.reduce(...args);};
constsum=(x,y)=>x+y;constsquare=x=>x*x;letdata=[1,1,3,5,5];letmean=reduce(data,sum)/data.length;letdeviations=map(data,x=>x-mean);letstddev=Math.sqrt(reduce(map(deviations,square),sum)/(data.length-1));stddev// => 2
// This higher-order function returns a new function that passes its// arguments to f and returns the logical negation of f's return value;functionnot(f){returnfunction(...args){// Return a new functionletresult=f.apply(this,args);// that calls freturn!result;// and negates its result.};}consteven=x=>x%2===0;// A function to determine if a number is evenconstodd=not(even);// A new function that does the opposite[1,1,3,5,5].every(odd)// => true: every element of the array is odd
// Return a function that expects an array argument and applies f to// each element, returning the array of return values.// Contrast this with the map() function from earlier.functionmapper(f){returna=>map(a,f);}constincrement=x=>x+1;constincrementAll=mapper(increment);incrementAll([1,2,3])// => [2,3,4]
// Return a new function that computes f(g(...)).// The returned function h passes all of its arguments to g, then passes// the return value of g to f, then returns the return value of f.// Both f and g are invoked with the same this value as h was invoked with.functioncompose(f,g){returnfunction(...args){// We use call for f because we're passing a single value and// apply for g because we're passing an array of values.returnf.call(this,g.apply(this,args));};}constsum=(x,y)=>x+y;constsquare=x=>x*x;compose(square,sum)(2,3)// => 25; the square of the sum
// The arguments to this function are passed on the leftfunctionpartialLeft(f,...outerArgs){returnfunction(...innerArgs){// Return this functionletargs=[...outerArgs,...innerArgs];// Build the argument listreturnf.apply(this,args);// Then invoke f with it};}// The arguments to this function are passed on the rightfunctionpartialRight(f,...outerArgs){returnfunction(...innerArgs){// Return this functionletargs=[...innerArgs,...outerArgs];// Build the argument listreturnf.apply(this,args);// Then invoke f with it};}// The arguments to this function serve as a template. Undefined values// in the argument list are filled in with values from the inner set.functionpartial(f,...outerArgs){returnfunction(...innerArgs){letargs=[...outerArgs];// local copy of outer args templateletinnerIndex=0;// which inner arg is next// Loop through the args, filling in undefined values from inner argsfor(leti=0;i<args.length;i++){if(args[i]===undefined)args[i]=innerArgs[innerIndex++];}// Now append any remaining inner argumentsargs.push(...innerArgs.slice(innerIndex));returnf.apply(this,args);};}// Here is a function with three argumentsconstf=function(x,y,z){returnx*(y-z);};// Notice how these three partial applications differpartialLeft(f,2)(3,4)// => -2: Bind first argument: 2 * (3 - 4)partialRight(f,2)(3,4)// => 6: Bind last argument: 3 * (4 - 2)partial(f,undefined,2)(3,4)// => -6: Bind middle argument: 3 * (2 - 4)
constincrement=partialLeft(sum,1);constcuberoot=partialRight(Math.pow,1/3);cuberoot(increment(26))// => 3
constnot=partialLeft(compose,x=>!x);consteven=x=>x%2===0;constodd=not(even);constisNumber=not(isNaN);odd(3)&&isNumber(2)// => true
// sum() and square() functions are defined above. Here are some more:constproduct=(x,y)=>x*y;constneg=partial(product,-1);constsqrt=partial(Math.pow,undefined,.5);constreciprocal=partial(Math.pow,undefined,neg(1));// Now compute the mean and standard deviation.letdata=[1,1,3,5,5];// Our dataletmean=product(reduce(data,sum),reciprocal(data.length));letstddev=sqrt(product(reduce(map(data,compose(square,partial(sum,neg(mean)))),sum),reciprocal(sum(data.length,neg(1)))));[mean,stddev]// => [3, 2]
// Return a memoized version of f.// It only works if arguments to f all have distinct string representations.functionmemoize(f){constcache=newMap();// Value cache stored in the closure.returnfunction(...args){// Create a string version of the arguments to use as a cache key.letkey=args.length+args.join("+");if(cache.has(key)){returncache.get(key);}else{letresult=f.apply(this,args);cache.set(key,result);returnresult;}};}
// Return the Greatest Common Divisor of two integers using the Euclidian// algorithm: http://en.wikipedia.org/wiki/Euclidean_algorithmfunctiongcd(a,b){// Type checking for a and b has been omittedif(a<b){// Ensure that a >= b when we start[a,b]=[b,a];// Destructuring assignment to swap variables}while(b!==0){// This is Euclid's algorithm for GCD[a,b]=[b,a%b];}returna;}constgcdmemo=memoize(gcd);gcdmemo(85,187)// => 17// Note that when we write a recursive function that we will be memoizing,// we typically want to recurse to the memoized version, not the original.constfactorial=memoize(function(n){return(n<=1)?1:n*factorial(n-1);});factorial(5)// => 120: also caches values for 4, 3, 2 and 1.
You can define functions with the function keyword and with the ES6
=> arrow syntax.
You can invoke functions, which can be used as methods and constructors.
Some ES6 features allow you to define default values for optional function parameters, to gather multiple arguments into an array using a rest parameter, and to destructure object and array arguments into function parameters.
You can use the ... spread operator to pass the elements of an
array or other iterable object as arguments in a function
invocation.
A function defined inside of and returned by an enclosing function retains access to its lexical scope and can therefore read and write the variables defined inside the outer function. Functions used in this way are called closures, and this is a technique that is worth understanding.
Functions are objects that can be manipulated by JavaScript, and this enables a functional style of programming.
1 The term was coined by Martin Fowler. See http://martinfowler.com/dslCatalog/methodChaining.html.
2 If you are familiar with Python, note that this is different than Python, in which every invocation shares the same default value.
3 This may not seem like a particularly interesting point unless you are familiar with more static languages, in which functions are part of a program but cannot be manipulated by the program.
// This is a factory function that returns a new range object.functionrange(from,to){// Use Object.create() to create an object that inherits from the// prototype object defined below. The prototype object is stored as// a property of this function, and defines the shared methods (behavior)// for all range objects.letr=Object.create(range.methods);// Store the start and end points (state) of this new range object.// These are noninherited properties that are unique to this object.r.from=from;r.to=to;// Finally return the new objectreturnr;}// This prototype object defines methods inherited by all range objects.range.methods={// Return true if x is in the range, false otherwise// This method works for textual and Date ranges as well as numeric.includes(x){returnthis.from<=x&&x<=this.to;},// A generator function that makes instances of the class iterable.// Note that it only works for numeric ranges.*[Symbol.iterator](){for(letx=Math.ceil(this.from);x<=this.to;x++)yieldx;},// Return a string representation of the rangetoString(){return"("+this.from+"..."+this.to+")";}};// Here are example uses of a range object.letr=range(1,3);// Create a range objectr.includes(2)// => true: 2 is in the ranger.toString()// => "(1...3)"[...r]// => [1, 2, 3]; convert to an array via iterator
This code defines a factory function range() for creating new
Range objects.
It uses the methods property of this range() function as a
convenient place to store the prototype object that defines the
class. There is nothing special or idiomatic about putting the
prototype object here.
The range() function defines from and to properties on each
Range object. These are the unshared, noninherited properties that
define the unique state of each individual Range object.
The range.methods object uses the ES6 shorthand syntax for
defining methods, which is why you don’t see the function keyword
anywhere. (See §6.10.5 to
review object literal shorthand method syntax.)
One of the methods in the prototype has the computed name
(§6.10.2) Symbol.iterator, which means that it
is defining an iterator for Range objects. The name of this method
is prefixed with *, which indicates that it is a generator function
instead of a regular function. Iterators and generators are covered
in detail in Chapter 12. For now, the upshot is that instances of
this Range class can be used with the for/of loop and with the
... spread operator.
The shared, inherited methods defined in range.methods all use the
from and to properties that were initialized in the range()
factory function. In order to refer to them, they use the this
keyword to refer to the object through which they were invoked. This
use of this is a fundamental characteristic of the methods of any
class.
// This is a constructor function that initializes new Range objects.// Note that it does not create or return the object. It just initializes this.functionRange(from,to){// Store the start and end points (state) of this new range object.// These are noninherited properties that are unique to this object.this.from=from;this.to=to;}// All Range objects inherit from this object.// Note that the property name must be "prototype" for this to work.Range.prototype={// Return true if x is in the range, false otherwise// This method works for textual and Date ranges as well as numeric.includes:function(x){returnthis.from<=x&&x<=this.to;},// A generator function that makes instances of the class iterable.// Note that it only works for numeric ranges.[Symbol.iterator]:function*(){for(letx=Math.ceil(this.from);x<=this.to;x++)yieldx;},// Return a string representation of the rangetoString:function(){return"("+this.from+"..."+this.to+")";}};// Here are example uses of this new Range classletr=newRange(1,3);// Create a Range object; note the use of newr.includes(2)// => true: 2 is in the ranger.toString()// => "(1...3)"[...r]// => [1, 2, 3]; convert to an array via iterator
rinstanceofRange// => true: r inherits from Range.prototype
functionStrange(){}Strange.prototype=Range.prototype;newStrange()instanceofRange// => true
range.methods.isPrototypeOf(r);// range.methods is the prototype object.
letF=function(){};// This is a function object.letp=F.prototype;// This is the prototype object associated with F.letc=p.constructor;// This is the function associated with the prototype.c===F// => true: F.prototype.constructor === F for any F
leto=newF();// Create an object o of class Fo.constructor===F// => true: the constructor property specifies the class
Range.prototype={constructor:Range,// Explicitly set the constructor back-reference/* method definitions go here */};
// Extend the predefined Range.prototype object so we don't overwrite// the automatically created Range.prototype.constructor property.Range.prototype.includes=function(x){returnthis.from<=x&&x<=this.to;};Range.prototype.toString=function(){return"("+this.from+"..."+this.to+")";};
classRange{constructor(from,to){// Store the start and end points (state) of this new range object.// These are noninherited properties that are unique to this object.this.from=from;this.to=to;}// Return true if x is in the range, false otherwise// This method works for textual and Date ranges as well as numeric.includes(x){returnthis.from<=x&&x<=this.to;}// A generator function that makes instances of the class iterable.// Note that it only works for numeric ranges.*[Symbol.iterator](){for(letx=Math.ceil(this.from);x<=this.to;x++)yieldx;}// Return a string representation of the rangetoString(){return`(${this.from}...${this.to})`;}}// Here are example uses of this new Range classletr=newRange(1,3);// Create a Range objectr.includes(2)// => true: 2 is in the ranger.toString()// => "(1...3)"[...r]// => [1, 2, 3]; convert to an array via iterator
The class is declared with the class keyword, which is followed by
the name of class and a class body in curly braces.
The class body includes method definitions that use object literal
method shorthand (which we also used in Example 9-1), where the
function keyword is omitted. Unlike object literals, however, no
commas are used to separate the methods from each other. (Although
class bodies are superficially similar to object literals, they are
not the same thing. In particular, they do not support the
definition of properties with name/value pairs.)
The keyword constructor is used to define the constructor function
for the class. The function defined is not actually named
“constructor”, however. The class declaration statement defines a
new variable Range and assigns the value of this special
constructor function to that variable.
If your class does not need to do any initialization, you can omit
the constructor keyword and its body, and an empty constructor
function will be implicitly created for you.
// A Span is like a Range, but instead of initializing it with// a start and an end, we initialize it with a start and a lengthclassSpanextendsRange{constructor(start,length){if(length>=0){super(start,start+length);}else{super(start+length,start);}}}
letsquare=function(x){returnx*x;};square(3)// => 9
letSquare=class{constructor(x){this.area=x*x;}};newSquare(3).area// => 9
All code within the body of a class declaration is implicitly in
strict mode (§5.6.3), even if no "use strict" directive
appears. This means, for example, that you can’t use octal integer
literals or the with statement within class bodies and that you
are more likely to get syntax errors if you forget to declare a
variable before using it.
Unlike function declarations, class declarations are not “hoisted.” Recall from §8.1.1 that function definitions behave as if they had been moved to the top of the enclosing file or enclosing function, meaning that you can invoke a function in code that comes before the actual definition of the function. Although class declarations are like function declarations in some ways, they do not share this hoisting behavior: you cannot instantiate a class before you declare it.
staticparse(s){letmatches=s.match(/^\((\d+)\.\.\.(\d+)\)$/);if(!matches){thrownewTypeError(`Cannot parse Range from "${s}".`)}returnnewRange(parseInt(matches[1]),parseInt(matches[2]));}
letr=Range.parse('(1...10)');// Returns a new Range objectr.parse('(1...10)');// TypeError: r.parse is not a function
*[Symbol.iterator](){for(letx=Math.ceil(this.from);x<=this.to;x++)yieldx;}
classBuffer{constructor(){this.size=0;this.capacity=4096;this.buffer=newUint8Array(this.capacity);}}
classBuffer{size=0;capacity=4096;buffer=newUint8Array(this.capacity);}
classBuffer{#size=0;getsize(){returnthis.#size;}}
staticintegerRangePattern=/^\((\d+)\.\.\.(\d+)\)$/;staticparse(s){letmatches=s.match(Range.integerRangePattern);if(!matches){thrownewTypeError(`Cannot parse Range from "${s}".`)}returnnewRange(parseInt(matches[1]),matches[2]);}
/*** Instances of this Complex class represent complex numbers.* Recall that a complex number is the sum of a real number and an* imaginary number and that the imaginary number i is the square root of -1.*/classComplex{// Once class field declarations are standardized, we could declare// private fields to hold the real and imaginary parts of a complex number// here, with code like this://// #r = 0;// #i = 0;// This constructor function defines the instance fields r and i on every// instance it creates. These fields hold the real and imaginary parts of// the complex number: they are the state of the object.constructor(real,imaginary){this.r=real;// This field holds the real part of the number.this.i=imaginary;// This field holds the imaginary part.}// Here are two instance methods for addition and multiplication// of complex numbers. If c and d are instances of this class, we// might write c.plus(d) or d.times(c)plus(that){returnnewComplex(this.r+that.r,this.i+that.i);}times(that){returnnewComplex(this.r*that.r-this.i*that.i,this.r*that.i+this.i*that.r);}// And here are static variants of the complex arithmetic methods.// We could write Complex.sum(c,d) and Complex.product(c,d)staticsum(c,d){returnc.plus(d);}staticproduct(c,d){returnc.times(d);}// These are some instance methods that are defined as getters// so they're used like fields. The real and imaginary getters would// be useful if we were using private fields this.#r and this.#igetreal(){returnthis.r;}getimaginary(){returnthis.i;}getmagnitude(){returnMath.hypot(this.r,this.i);}// Classes should almost always have a toString() methodtoString(){return`{${this.r},${this.i}}`;}// It is often useful to define a method for testing whether// two instances of your class represent the same valueequals(that){returnthatinstanceofComplex&&this.r===that.r&&this.i===that.i;}// Once static fields are supported inside class bodies, we could// define a useful Complex.ZERO constant like this:// static ZERO = new Complex(0,0);}// Here are some class fields that hold useful predefined complex numbers.Complex.ZERO=newComplex(0,0);Complex.ONE=newComplex(1,0);Complex.I=newComplex(0,1);
letc=newComplex(2,3);// Create a new object with the constructorletd=newComplex(c.i,c.r);// Use instance fields of cc.plus(d).toString()// => "{5,5}"; use instance methodsc.magnitude// => Math.hypot(2,3); use a getter functionComplex.product(c,d)// => new Complex(0, 13); a static methodComplex.ZERO.toString()// => "{0,0}"; a static property
// Return a complex number that is the complex conjugate of this one.Complex.prototype.conj=function(){returnnewComplex(this.r,-this.i);};
// If the new String method startsWith() is not already defined...if(!String.prototype.startsWith){// ...then define it like this using the older indexOf() method.String.prototype.startsWith=function(s){returnthis.indexOf(s)===0;};}
// Invoke the function f this many times, passing the iteration number// For example, to print "hello" 3 times:// let n = 3;// n.times(i => { console.log(`hello ${i}`); });Number.prototype.times=function(f,context){letn=this.valueOf();for(leti=0;i<n;i++)f.call(context,i);};
// This is the constructor function for our subclassfunctionSpan(start,span){if(span>=0){this.from=start;this.to=start+span;}else{this.to=start;this.from=start+span;}}// Ensure that the Span prototype inherits from the Range prototypeSpan.prototype=Object.create(Range.prototype);// We don't want to inherit Range.prototype.constructor, so we// define our own constructor property.Span.prototype.constructor=Span;// By defining its own toString() method, Span overrides the// toString() method that it would otherwise inherit from Range.Span.prototype.toString=function(){return`(${this.from}... +${this.to-this.from})`;};
Span.prototype=Object.create(Range.prototype);
// A trivial Array subclass that adds getters for the first and last elements.classEZArrayextendsArray{getfirst(){returnthis[0];}getlast(){returnthis[this.length-1];}}leta=newEZArray();ainstanceofEZArray// => true: a is subclass instanceainstanceofArray// => true: a is also a superclass instance.a.push(1,2,3,4);// a.length == 4; we can use inherited methodsa.pop()// => 4: another inherited methoda.first// => 1: first getter defined by subclassa.last// => 3: last getter defined by subclassa[1]// => 2: regular array access syntax still works.Array.isArray(a)// => true: subclass instance really is an arrayEZArray.isArray(a)// => true: subclass inherits static methods, too!
// EZArray inherits instance methods because EZArray.prototype// inherits from Array.prototypeArray.prototype.isPrototypeOf(EZArray.prototype)// => true// And EZArray inherits static methods and properties because// EZArray inherits from Array. This is a special feature of the// extends keyword and is not possible before ES6.Array.isPrototypeOf(EZArray)// => true
classTypedMapextendsMap{constructor(keyType,valueType,entries){// If entries are specified, check their typesif(entries){for(let[k,v]ofentries){if(typeofk!==keyType||typeofv!==valueType){thrownewTypeError(`Wrong type for entry [${k},${v}]`);}}}// Initialize the superclass with the (type-checked) initial entriessuper(entries);// And then initialize this subclass by storing the typesthis.keyType=keyType;this.valueType=valueType;}// Now redefine the set() method to add type checking for any// new entries added to the map.set(key,value){// Throw an error if the key or value are of the wrong typeif(this.keyType&&typeofkey!==this.keyType){thrownewTypeError(`${key}is not of type${this.keyType}`);}if(this.valueType&&typeofvalue!==this.valueType){thrownewTypeError(`${value}is not of type${this.valueType}`);}// If the types are correct, we invoke the superclass's version of// the set() method, to actually add the entry to the map. And we// return whatever the superclass method returns.returnsuper.set(key,value);}}
If you define a class with the extends keyword, then the
constructor for your class must use super() to invoke the
superclass constructor.
If you don’t define a constructor in your subclass, one will be
defined automatically for you. This implicitly defined constructor
simply takes whatever values are passed to it and passes those
values to super().
You may not use the this keyword in your constructor until after
you have invoked the superclass constructor with super(). This
enforces a rule that superclasses get to initialize themselves
before subclasses do.
The special expression new.target is undefined in functions that
are invoked without the new keyword. In constructor functions,
however, new.target is a reference to the constructor that was
invoked. When a subclass constructor is invoked and uses super()
to invoke the superclass constructor, that superclass constructor
will see the subclass constructor as the value of new.target. A
well-designed superclass should not need to know whether it has been
subclassed, but it might be useful to be able to use
new.target.name in logging messages, for example.
/*** A Set-like class that keeps track of how many times a value has* been added. Call add() and remove() like you would for a Set, and* call count() to find out how many times a given value has been added.* The default iterator yields the values that have been added at least* once. Use entries() if you want to iterate [value, count] pairs.*/classHistogram{// To initialize, we just create a Map object to delegate toconstructor(){this.map=newMap();}// For any given key, the count is the value in the Map, or zero// if the key does not appear in the Map.count(key){returnthis.map.get(key)||0;}// The Set-like method has() returns true if the count is non-zerohas(key){returnthis.count(key)>0;}// The size of the histogram is just the number of entries in the Map.getsize(){returnthis.map.size;}// To add a key, just increment its count in the Map.add(key){this.map.set(key,this.count(key)+1);}// Deleting a key is a little trickier because we have to delete// the key from the Map if the count goes back down to zero.delete(key){letcount=this.count(key);if(count===1){this.map.delete(key);}elseif(count>1){this.map.set(key,count-1);}}// Iterating a Histogram just returns the keys stored in it[Symbol.iterator](){returnthis.map.keys();}// These other iterator methods just delegate to the Map objectkeys(){returnthis.map.keys();}values(){returnthis.map.values();}entries(){returnthis.map.entries();}}
/*** The AbstractSet class defines a single abstract method, has().*/classAbstractSet{// Throw an error here so that subclasses are forced// to define their own working version of this method.has(x){thrownewError("Abstract method");}}/*** NotSet is a concrete subclass of AbstractSet.* The members of this set are all values that are not members of some* other set. Because it is defined in terms of another set it is not* writable, and because it has infinite members, it is not enumerable.* All we can do with it is test for membership and convert it to a* string using mathematical notation.*/classNotSetextendsAbstractSet{constructor(set){super();this.set=set;}// Our implementation of the abstract method we inheritedhas(x){return!this.set.has(x);}// And we also override this Object methodtoString(){return`{ x| x ∉${this.set.toString()}}`;}}/*** Range set is a concrete subclass of AbstractSet. Its members are* all values that are between the from and to bounds, inclusive.* Since its members can be floating point numbers, it is not* enumerable and does not have a meaningful size.*/classRangeSetextendsAbstractSet{constructor(from,to){super();this.from=from;this.to=to;}has(x){returnx>=this.from&&x<=this.to;}toString(){return`{ x|${this.from}≤ x ≤${this.to}}`;}}/** AbstractEnumerableSet is an abstract subclass of AbstractSet. It defines* an abstract getter that returns the size of the set and also defines an* abstract iterator. And it then implements concrete isEmpty(), toString(),* and equals() methods on top of those. Subclasses that implement the* iterator, the size getter, and the has() method get these concrete* methods for free.*/classAbstractEnumerableSetextendsAbstractSet{getsize(){thrownewError("Abstract method");}[Symbol.iterator](){thrownewError("Abstract method");}isEmpty(){returnthis.size===0;}toString(){return`{${Array.from(this).join(", ")}}`;}equals(set){// If the other set is not also Enumerable, it isn't equal to this oneif(!(setinstanceofAbstractEnumerableSet))returnfalse;// If they don't have the same size, they're not equalif(this.size!==set.size)returnfalse;// Loop through the elements of this setfor(letelementofthis){// If an element isn't in the other set, they aren't equalif(!set.has(element))returnfalse;}// The elements matched, so the sets are equalreturntrue;}}/** SingletonSet is a concrete subclass of AbstractEnumerableSet.* A singleton set is a read-only set with a single member.*/classSingletonSetextendsAbstractEnumerableSet{constructor(member){super();this.member=member;}// We implement these three methods, and inherit isEmpty, equals()// and toString() implementations based on these methods.has(x){returnx===this.member;}getsize(){return1;}*[Symbol.iterator](){yieldthis.member;}}/** AbstractWritableSet is an abstract subclass of AbstractEnumerableSet.* It defines the abstract methods insert() and remove() that insert and* remove individual elements from the set, and then implements concrete* add(), subtract(), and intersect() methods on top of those. Note that* our API diverges here from the standard JavaScript Set class.*/classAbstractWritableSetextendsAbstractEnumerableSet{insert(x){thrownewError("Abstract method");}remove(x){thrownewError("Abstract method");}add(set){for(letelementofset){this.insert(element);}}subtract(set){for(letelementofset){this.remove(element);}}intersect(set){for(letelementofthis){if(!set.has(element)){this.remove(element);}}}}/*** A BitSet is a concrete subclass of AbstractWritableSet with a* very efficient fixed-size set implementation for sets whose* elements are non-negative integers less than some maximum size.*/classBitSetextendsAbstractWritableSet{constructor(max){super();this.max=max;// The maximum integer we can store.this.n=0;// How many integers are in the setthis.numBytes=Math.floor(max/8)+1;// How many bytes we needthis.data=newUint8Array(this.numBytes);// The bytes}// Internal method to check if a value is a legal member of this set_valid(x){returnNumber.isInteger(x)&&x>=0&&x<=this.max;}// Tests whether the specified bit of the specified byte of our// data array is set or not. Returns true or false._has(byte,bit){return(this.data[byte]&BitSet.bits[bit])!==0;}// Is the value x in this BitSet?has(x){if(this._valid(x)){letbyte=Math.floor(x/8);letbit=x%8;returnthis._has(byte,bit);}else{returnfalse;}}// Insert the value x into the BitSetinsert(x){if(this._valid(x)){// If the value is validletbyte=Math.floor(x/8);// convert to byte and bitletbit=x%8;if(!this._has(byte,bit)){// If that bit is not set yetthis.data[byte]|=BitSet.bits[bit];// then set itthis.n++;// and increment set size}}else{thrownewTypeError("Invalid set element: "+x);}}remove(x){if(this._valid(x)){// If the value is validletbyte=Math.floor(x/8);// compute the byte and bitletbit=x%8;if(this._has(byte,bit)){// If that bit is already setthis.data[byte]&=BitSet.masks[bit];// then unset itthis.n--;// and decrement size}}else{thrownewTypeError("Invalid set element: "+x);}}// A getter to return the size of the setgetsize(){returnthis.n;}// Iterate the set by just checking each bit in turn.// (We could be a lot more clever and optimize this substantially)*[Symbol.iterator](){for(leti=0;i<=this.max;i++){if(this.has(i)){yieldi;}}}}// Some pre-computed values used by the has(), insert() and remove() methodsBitSet.bits=newUint8Array([1,2,4,8,16,32,64,128]);BitSet.masks=newUint8Array([~1,~2,~4,~8,~16,~32,~64,~128]);
Objects that are members of the same class inherit properties from
the same prototype object. The prototype object is the key feature
of JavaScript classes, and it is possible to define classes with
nothing more than the Object.create() method.
Prior to ES6, classes were more typically defined by first defining
a constructor function. Functions created with the function
keyword have a prototype property, and the value of this property
is an object that is used as the prototype of all objects created
when the function is invoked with new as a constructor. By
initializing this prototype object, you can define the shared methods
of your class. Although the prototype object is the key
feature of the class, the constructor function is the public
identity of the class.
ES6 introduces a class keyword that makes it easier to define
classes, but under the hood, constructor and prototype mechanism
remains the same.
Subclasses are defined using the extends keyword in a class
declaration.
Subclasses can invoke the constructor of their superclass or
overridden methods of their superclass with the super keyword.
1 Except functions returned by the ES5 Function.bind() method. Bound functions have no prototype property of their own, but they use the prototype of the underlying function if they are invoked as constructors.
2 See Design Patterns (Addison-Wesley Professional) by Erich Gamma et al. or Effective Java (Addison-Wesley Professional) by Joshua Bloch, for example.
Do-it-yourself modules with classes, objects, and closures
Node modules using require()
ES6 modules using export, import, and import()
constBitSet=(function(){// Set BitSet to the return value of this function// Private implementation details herefunctionisValid(set,n){...}functionhas(set,byte,bit){...}constBITS=newUint8Array([1,2,4,8,16,32,64,128]);constMASKS=newUint8Array([~1,~2,~4,~8,~16,~32,~64,~128]);// The public API of the module is just the BitSet class, which we define// and return here. The class can use the private functions and constants// defined above, but they will be hidden from users of the classreturnclassBitSetextendsAbstractWritableSet{// ... implementation omitted ...};}());
// This is how we could define a stats moduleconststats=(function(){// Utility functions private to the moduleconstsum=(x,y)=>x+y;constsquare=x=>x*x;// A public function that will be exportedfunctionmean(data){returndata.reduce(sum)/data.length;}// A public function that we will exportfunctionstddev(data){letm=mean(data);returnMath.sqrt(data.map(x=>x-m).map(square).reduce(sum)/(data.length-1));}// We export the public function as properties of an objectreturn{mean,stddev};}());// And here is how we might use the modulestats.mean([1,3,5,7,9])// => 5stats.stddev([1,3,5,7,9])// => Math.sqrt(10)
constmodules={};functionrequire(moduleName){returnmodules[moduleName];}modules["sets.js"]=(function(){constexports={};// The contents of the sets.js file go here:exports.BitSet=classBitSet{...};returnexports;}());modules["stats.js"]=(function(){constexports={};// The contents of the stats.js file go here:constsum=(x,y)=>x+y;constsquare=x=>x*x;exports.mean=function(data){...};exports.stddev=function(data){...};returnexports;}());
// Get references to the modules (or the module content) that we needconststats=require("stats.js");constBitSet=require("sets.js").BitSet;// Now write code using those moduleslets=newBitSet(100);s.insert(10);s.insert(20);s.insert(30);letaverage=stats.mean([...s]);// average is 20
constsum=(x,y)=>x+y;constsquare=x=>x*x;exports.mean=data=>data.reduce(sum)/data.length;exports.stddev=function(d){letm=exports.mean(d);returnMath.sqrt(d.map(x=>x-m).map(square).reduce(sum)/(d.length-1));};
module.exports=classBitSetextendsAbstractWritableSet{// implementation omitted};
// Define all the functions, public and privateconstsum=(x,y)=>x+y;constsquare=x=>x*x;constmean=data=>data.reduce(sum)/data.length;conststddev=d=>{letm=mean(d);returnMath.sqrt(d.map(x=>x-m).map(square).reduce(sum)/(d.length-1));};// Now export only the public onesmodule.exports={mean,stddev};
// These modules are built in to Nodeconstfs=require("fs");// The built-in filesystem moduleconsthttp=require("http");// The built-in HTTP module// The Express HTTP server framework is a third-party module.// It is not part of Node but has been installed locallyconstexpress=require("express");
conststats=require('./stats.js');constBitSet=require('./utils/bitset.js');
// Import the entire stats object, with all of its functionsconststats=require('./stats.js');// We've got more functions than we need, but they're neatly// organized into a convenient "stats" namespace.letaverage=stats.mean(data);// Alternatively, we can use idiomatic destructuring assignment to import// exactly the functions we want directly into the local namespace:const{stddev}=require('./stats.js');// This is nice and succinct, though we lose a bit of context// without the 'stats' prefix as a namspace for the stddev() function.letsd=stddev(data);
exportconstPI=Math.PI;exportfunctiondegreesToRadians(d){returnd*PI/180;}exportclassCircle{constructor(r){this.r=r;}area(){returnPI*this.r*this.r;}}
export{Circle,degreesToRadians,PI};
exportdefaultclassBitSet{// implementation omitted}
importBitSetfrom'./bitset.js';
import{mean,stddev}from"./stats.js";
import*asstatsfrom"./stats.js";
importHistogram,{mean,stddev}from"./histogram-stats.js";
import"./analytics.js";
import{renderasrenderImage}from"./imageutils.js";import{renderasrenderUI}from"./ui.js";
import{defaultasHistogram,mean,stddev}from"./histogram-stats.js";
export{layoutascalculateLayout,renderasrenderLayout};
export{Math.sinassin,Math.cosascos};// SyntaxError
import{mean}from"./stats/mean.js";import{stddev}from"./stats/stddev.js";export{mean,stdev};
export{mean}from"./stats/mean.js";export{stddev}from"./stats/stddev.js";
export*from"./stats/mean.js";export*from"./stats/stddev.js";
export{mean,meanasaverage}from"./stats/mean.js";export{stddev}from"./stats/stddev.js";
export{defaultasmean}from"./stats/mean.js";export{defaultasstddev}from"./stats/stddev.js";
// Import the mean() function from ./stats.js and make it the// default export of this moduleexport{meanasdefault}from"./stats.js"
// The average.js module simply re-exports the stats/mean.js default exportexport{default}from"./stats/mean.js"
<scripttype="module">import"./main.js";</script>
import*asstatsfrom"./stats.js";
import("./stats.js").then(stats=>{letaverage=stats.mean(data);})
asyncanalyzeData(data){letstats=awaitimport("./stats.js");return{average:stats.mean(data),stddev:stats.stddev(data)};}
functionlocalStringsURL(locale){returnnewURL(`l10n/${locale}.json`,import.meta.url);}
In the early days of JavaScript, modularity could only be achieved through the clever use of immediately invoked function expressions.
Node added its own module system on top of the JavaScript
language. Node modules are imported with require() and define
their exports by setting properties of the Exports object, or by
setting the module.exports property.
In ES6, JavaScript finally got its own module system with import
and export keywords, and ES2020 is adding support for dynamic
imports with import().
1 For example: web apps that have frequent incremental updates and users who make frequent return visits may find that using small modules instead of large bundles can result in better average load times because of better utilization of the user’s browser cache.
The Set and Map classes for representing sets of values and mappings from one set of values to another set of values.
Array-like objects known as TypedArrays that represent arrays of binary data, along with a related class for extracting values from non-array binary data.
Regular expressions and the RegExp class, which define textual patterns and are useful for text processing. This section also covers regular expression syntax in detail.
The Date class for representing and manipulating dates and times.
The Error class and its various subclasses, instances of which are thrown when errors occur in JavaScript programs.
The JSON object, whose methods support serialization and deserialization of JavaScript data structures composed of objects, arrays, strings, numbers, and booleans.
The Intl object and the classes it defines that can help you localize your JavaScript programs.
The Console object, whose methods output strings in ways that are particularly useful for debugging programs and logging the behavior of those programs.
The URL class, which simplifies the task of parsing and manipulating URLs. This section also covers global functions for encoding and decoding URLs and their component parts.
setTimeout() and related functions for specifying code to be
executed after a specified interval of time has elapsed.
lets=newSet();// A new, empty setlett=newSet([1,s]);// A new set with two members
lett=newSet(s);// A new set that copies the elements of s.letunique=newSet("Mississippi");// 4 elements: "M", "i", "s", and "p"
unique.size// => 4
lets=newSet();// Start emptys.size// => 0s.add(1);// Add a numbers.size// => 1; now the set has one members.add(1);// Add the same number agains.size// => 1; the size does not changes.add(true);// Add another value; note that it is fine to mix typess.size// => 2s.add([1,2,3]);// Add an array values.size// => 3; the array was added, not its elementss.delete(1)// => true: successfully deleted element 1s.size// => 2: the size is back down to 2s.delete("test")// => false: "test" was not a member, deletion faileds.delete(true)// => true: delete succeededs.delete([1,2,3])// => false: the array in the set is differents.size// => 1: there is still that one array in the sets.clear();// Remove everything from the sets.size// => 0
The add() method takes a single argument; if you pass an array, it
adds the array itself to the set, not the individual array
elements. add() always returns the set it is invoked on, however,
so if you want to add multiple values to a set, you can used chained
method calls like s.add('a').add('b').add('c');.
The delete() method also only deletes a single set element at a
time. Unlike add(), however, delete() returns a boolean value. If
the value you specify was actually a member of the set, then
delete() removes it and returns true. Otherwise, it does nothing
and returns false.
Finally, it is very important to understand that set membership is
based on strict equality checks, like the === operator
performs. A set can contain both the number 1 and the string
"1", because it considers them to be distinct values. When the
values are objects (or arrays or functions), they are also compared
as if with ===. This is why we were unable to delete the array
element from the set in this code. We added an array to the
set and then tried to remove that array by passing a
different array (albeit with the same elements) to the delete()
method. In order for this to work, we would have had to pass a
reference to exactly the same array.
letoneDigitPrimes=newSet([2,3,5,7]);oneDigitPrimes.has(2)// => true: 2 is a one-digit prime numberoneDigitPrimes.has(3)// => true: so is 3oneDigitPrimes.has(4)// => false: 4 is not a primeoneDigitPrimes.has("5")// => false: "5" is not even a number
letsum=0;for(letpofoneDigitPrimes){// Loop through the one-digit primessum+=p;// and add them up}sum// => 17: 2 + 3 + 5 + 7
[...oneDigitPrimes]// => [2,3,5,7]: the set converted to an ArrayMath.max(...oneDigitPrimes)// => 7: set elements passed as function arguments
letproduct=1;oneDigitPrimes.forEach(n=>{product*=n;});product// => 210: 2 * 3 * 5 * 7
letm=newMap();// Create a new, empty mapletn=newMap([// A new map initialized with string keys mapped to numbers["one",1],["two",2]]);
letcopy=newMap(n);// A new map with the same keys and values as map nleto={x:1,y:2};// An object with two propertiesletp=newMap(Object.entries(o));// Same as new map([["x", 1], ["y", 2]])
letm=newMap();// Start with an empty mapm.size// => 0: empty maps have no keysm.set("one",1);// Map the key "one" to the value 1m.set("two",2);// And the key "two" to the value 2.m.size// => 2: the map now has two keysm.get("two")// => 2: return the value associated with key "two"m.get("three")// => undefined: this key is not in the setm.set("one",true);// Change the value associated with an existing keym.size// => 2: the size doesn't changem.has("one")// => true: the map has a key "one"m.has(true)// => false: the map does not have a key truem.delete("one")// => true: the key existed and deletion succeededm.size// => 1m.delete("three")// => false: failed to delete a nonexistent keym.clear();// Remove all keys and values from the map
letm=newMap().set("one",1).set("two",2).set("three",3);m.size// => 3m.get("two")// => 2
letm=newMap();// Start with an empty map.m.set({},1);// Map one empty object to the number 1.m.set({},2);// Map a different empty object to the number 2.m.size// => 2: there are two keys in this mapm.get({})// => undefined: but this empty object is not a keym.set(m,undefined);// Map the map itself to the value undefined.m.has(m)// => true: m is a key in itselfm.get(m)// => undefined: same value we'd get if m wasn't a key
letm=newMap([["x",1],["y",2]]);[...m]// => [["x", 1], ["y", 2]]for(let[key,value]ofm){// On the first iteration, key will be "x" and value will be 1// On the second iteration, key will be "y" and value will be 2}
[...m.keys()]// => ["x", "y"]: just the keys[...m.values()]// => [1, 2]: just the values[...m.entries()]// => [["x", 1], ["y", 2]]: same as [...m]
m.forEach((value,key)=>{// note value, key NOT key, value// On the first invocation, value will be 1 and key will be "x"// On the second invocation, value will be 2 and key will be "y"});
WeakMap keys must be objects or arrays; primitive values are not subject to garbage collection and cannot be used as keys.
WeakMap implements only the get(), set(), has(), and delete()
methods. In particular, WeakMap is not iterable and does not define
keys(), values(), or forEach(). If WeakMap was iterable, then
its keys would be reachable and it wouldn’t be weak.
Similarly, WeakMap does not implement the size property because
the size of a WeakMap could change at any time as objects are
garbage collected.
WeakSet does not allow primitive values as members.
WeakSet implements only the add(), has(), and delete()
methods and is not iterable.
WeakSet does not have a size property.
The elements of a typed array are all numbers. Unlike regular JavaScript numbers, however, typed arrays allow you to specify the type (signed and unsigned integers and IEEE-754 floating point) and size (8 bits to 64 bits) of the numbers to be stored in the array.
You must specify the length of a typed array when you create it, and that length can never change.
The elements of a typed array are always initialized to 0 when the array is created.
| Constructor | Numeric type |
|---|---|
|
signed bytes |
|
unsigned bytes |
|
unsigned bytes without rollover |
|
signed 16-bit short integers |
|
unsigned 16-bit short integers |
|
signed 32-bit integers |
|
unsigned 32-bit integers |
|
signed 64-bit BigInt values (ES2020) |
|
unsigned 64-bit BigInt values (ES2020) |
|
32-bit floating-point value |
|
64-bit floating-point value: a regular JavaScript number |
letbytes=newUint8Array(1024);// 1024 bytesletmatrix=newFloat64Array(9);// A 3x3 matrixletpoint=newInt16Array(3);// A point in 3D spaceletrgba=newUint8ClampedArray(4);// A 4-byte RGBA pixel valueletsudoku=newInt8Array(81);// A 9x9 sudoku board
letwhite=Uint8ClampedArray.of(255,255,255,0);// RGBA opaque white
letints=Uint32Array.from(white);// The same 4 numbers, but as ints
// Floats truncated to ints, longer ints truncated to 8 bitsUint8Array.of(1.23,2.99,45000)// => new Uint8Array([1, 2, 200])
letbuffer=newArrayBuffer(1024*1024);buffer.byteLength// => 1024*1024; one megabyte of memory
letasbytes=newUint8Array(buffer);// Viewed as bytesletasints=newInt32Array(buffer);// Viewed as 32-bit signed intsletlastK=newUint8Array(buffer,1023*1024);// Last kilobyte as bytesletints2=newInt32Array(buffer,1024,256);// 2nd kilobyte as 256 integers
// Return the largest prime smaller than n, using the sieve of Eratosthenesfunctionsieve(n){leta=newUint8Array(n+1);// a[x] will be 1 if x is compositeletmax=Math.floor(Math.sqrt(n));// Don't do factors higher than thisletp=2;// 2 is the first primewhile(p<=max){// For primes less than maxfor(leti=2*p;i<=n;i+=p)// Mark multiples of p as compositea[i]=1;while(a[++p])/* empty */;// The next unmarked index is prime}while(a[n])n--;// Loop backward to find the last primereturnn;// And return it}
letints=newInt16Array(10);// 10 short integersints.fill(3).map(x=>x*x).join("")// => "9999999999"
letbytes=newUint8Array(1024);// A 1K bufferletpattern=newUint8Array([0,1,2,3]);// An array of 4 bytesbytes.set(pattern);// Copy them to the start of another byte arraybytes.set(pattern,4);// Copy them again at a different offsetbytes.set([0,1,2,3],8);// Or just copy values direct from a regular arraybytes.slice(0,12)// => new Uint8Array([0,1,2,3,0,1,2,3,0,1,2,3])
letints=newInt16Array([0,1,2,3,4,5,6,7,8,9]);// 10 short integersletlast3=ints.subarray(ints.length-3,ints.length);// Last 3 of themlast3[0]// => 7: this is the same as ints[7]
ints[9]=-1;// Change a value in the original array and...last3[2]// => -1: it also changes in the subarray
last3.buffer// The ArrayBuffer object for a typed arraylast3.buffer===ints.buffer// => true: both are views of the same bufferlast3.byteOffset// => 14: this view starts at byte 14 of the bufferlast3.byteLength// => 6: this view is 6 bytes (3 16-bit ints) longlast3.buffer.byteLength// => 20: but the underlying buffer has 20 bytes
a.length*a.BYTES_PER_ELEMENT===a.byteLength// => true
letbytes=newUint8Array(8);bytes[0]=1;// Set the first byte to 1bytes.buffer[0]// => undefined: buffer doesn't have index 0bytes.buffer[1]=255;// Try incorrectly to set a byte in the bufferbytes.buffer[1]// => 255: this just sets a regular JS propertybytes[1]// => 0: the line above did not set the byte
letbytes=newUint8Array(1024);// 1024 bytesletints=newUint32Array(bytes.buffer);// or 256 integersletfloats=newFloat64Array(bytes.buffer);// or 128 doubles
// If the integer 0x00000001 is arranged in memory as 01 00 00 00, then// we're on a little-endian platform. On a big-endian platform, we'd get// bytes 00 00 00 01 instead.letlittleEndian=newInt8Array(newInt32Array([1]).buffer)[0]===1;
// Assume we have a typed array of bytes of binary data to process. First,// we create a DataView object so we can flexibly read and write// values from those bytesletview=newDataView(bytes.buffer,bytes.byteOffset,bytes.byteLength);letint=view.getInt32(0);// Read big-endian signed int from byte 0int=view.getInt32(4,false);// Next int is also big-endianint=view.getUint32(8,true);// Next int is little-endian and unsignedview.setUint32(8,int,false);// Write it back in big-endian format
letpattern=/s$/;
letpattern=newRegExp("s$");
letpattern=/s$/i;
| Character | Matches |
|---|---|
Alphanumeric character |
Itself |
|
The NUL character ( |
|
Tab ( |
|
Newline ( |
|
Vertical tab ( |
|
Form feed ( |
|
Carriage return ( |
|
The Latin character specified by the hexadecimal number nn; for example, |
|
The Unicode character specified by the hexadecimal number xxxx; for example, |
|
The Unicode character specified by the codepoint n, where n is one to six hexadecimal digits between 0 and 10FFFF. Note that this syntax is only supported in regular expressions that use the |
|
The control character |
^ $ . * + ? = ! : | \ / ( ) [ ] { }
| Character | Matches |
|---|---|
|
Any one character between the brackets. |
|
Any one character not between the brackets. |
|
Any character except newline or another Unicode line terminator. Or, if the RegExp uses the |
|
Any ASCII word character. Equivalent to |
|
Any character that is not an ASCII word character. Equivalent to |
|
Any Unicode whitespace character. |
|
Any character that is not Unicode whitespace. |
|
Any ASCII digit. Equivalent to |
|
Any character other than an ASCII digit. Equivalent to |
|
A literal backspace (special case). |
| Character | Meaning |
|---|---|
|
Match the previous item at least n times but no more than m times. |
|
Match the previous item n or more times. |
|
Match exactly n occurrences of the previous item. |
|
Match zero or one occurrences of the previous item. That is, the previous item is optional. Equivalent to |
|
Match one or more occurrences of the previous item. Equivalent to |
|
Match zero or more occurrences of the previous item. Equivalent to |
letr=/\d{2,4}/;// Match between two and four digitsr=/\w{3}\d?/;// Match exactly three word characters and an optional digitr=/\s+java\s+/;// Match "java" with one or more spaces before and afterr=/[^(]*/;// Match zero or more characters that are not open parens
/([Jj]ava([Ss]cript)?)\sis\s(fun\w*)/
/['"][^'"]*['"]/
/(['"])[^'"]*\1/
/([Jj]ava(?:[Ss]cript)?)\sis\s(fun\w*)/
| Character | Meaning |
|---|---|
|
Alternation: match either the subexpression to the left or the subexpression to the right. |
|
Grouping: group items into a single unit that can be used with |
|
Grouping only: group items into a single unit, but do not remember the characters that match this group. |
|
Match the same characters that were matched when group number n was first matched. Groups are subexpressions within (possibly nested) parentheses. Group numbers are assigned by counting left parentheses from left to right. Groups formed with |
| Character | Meaning |
|---|---|
|
Match the beginning of the string or, with the |
|
Match the end of the string and, with the |
|
Match a word boundary. That is, match the position between a |
|
Match a position that is not a word boundary. |
|
A positive lookahead assertion. Require that the following characters match the pattern p, but do not include those characters in the match. |
|
A negative lookahead assertion. Require that the following characters do not match the pattern p. |
gThe g flag indicates that the regular expression is
“global”—that is, that we intend to use it to find all matches within
a string rather than just finding the first match. This flag does not
alter the way that pattern matching is done, but, as we’ll see later,
it does alter the behavior of the String match() method and the
RegExp exec() method in important ways.
iThe i flag specifies that pattern matching should be
case-insensitive.
mThe m flag specifies that matching should be done in
“multiline” mode. It says that the RegExp will be used with multiline
strings and that the ^ and $ anchors should match both the
beginning and end of the string and also the beginning and end of
individual lines within the string.
sLike the m flag, the s flag is also useful when working with
text that includes newlines. Normally, a “.” in a regular expression
matches any character except a line terminator. When the s flag is
used, however, “.” will match any character, including line
terminators. The s flag was added to JavaScript in ES2018 and, as of
early 2020, is supported in Node, Chrome, Edge, and Safari, but not Firefox.
uThe u flag stands for Unicode, and it makes the regular expression
match full Unicode codepoints rather than matching 16-bit
values. This flag was introduced in ES6, and you should make a habit
of using it on all regular expressions unless you have some reason not
to. If you do not use this flag, then your RegExps will not work well
with text that includes emoji and other characters (including many
Chinese characters) that require more than 16 bits. Without the u flag,
the “.” character matches any 1 UTF-16 16-bit value. With the flag,
however, “.” matches one Unicode codepoint, including those that have
more than 16 bits. Setting the u flag on a RegExp also allows you to use
the new \u{...} escape sequence for Unicode character and also
enables the \p{...} notation for Unicode character classes.
yThe y flag indicates that the regular expression is “sticky”
and should match at the beginning of a string or at the first
character following the previous match. When used with a regular
expression that is designed to find a single match, it effectively
treats that regular expression as if it begins with ^ to anchor it
to the beginning of the string. This flag is more useful with regular
expressions that are used repeatedly to find all matches within a
string. In this case, it causes special behavior of the String
match() method and the RegExp exec() method to enforce that each
subsequent match is anchored to the string position at which the last
one ended.
"JavaScript".search(/script/ui) // => 4"Python".search(/script/ui) // => -1
// No matter how it is capitalized, replace it with the correct capitalizationtext.replace(/javascript/gi,"JavaScript");
// A quote is a quotation mark, followed by any number of// nonquotation mark characters (which we capture), followed// by another quotation mark.letquote=/"([^"]*)"/g;// Replace the straight quotation marks with guillemets// leaving the quoted text (stored in $1) unchanged.'He said "stop"'.replace(quote,'«$1»')// => 'He said «stop»'
letquote=/"(?<quotedText>[^"]*)"/g;'He said "stop"'.replace(quote,'«$<quotedText>»')// => 'He said «stop»'
lets="15 times 15 is 225";s.replace(/\d+/gu, n => parseInt(n).toString(16)) // => "f times f is e1"
"7 plus 8 equals 15".match(/\d+/g)// => ["7", "8", "15"]
// A very simple URL parsing RegExpleturl=/(\w+):\/\/([\w.]+)\/(\S*)/;lettext="Visit my blog at http://www.example.com/~david";letmatch=text.match(url);letfullurl,protocol,host,path;if(match!==null){fullurl=match[0];// fullurl == "http://www.example.com/~david"protocol=match[1];// protocol == "http"host=match[2];// host == "www.example.com"path=match[3];// path == "~david"}
leturl=/(?<protocol>\w+):\/\/(?<host>[\w.]+)\/(?<path>\S*)/;lettext="Visit my blog at http://www.example.com/~david";letmatch=text.match(url);match[0]// => "http://www.example.com/~david"match.input// => textmatch.index// => 17match.groups.protocol// => "http"match.groups.host// => "www.example.com"match.groups.path// => "~david"
letvowel=/[aeiou]/y; // Sticky vowel match"test".match(vowel)// => null: "test" does not begin with a vowelvowel.lastIndex=1;// Specify a different match position"test".match(vowel)[0]// => "e": we found a vowel at position 1vowel.lastIndex// => 2: lastIndex was automatically updated"test".match(vowel)// => null: no vowel at position 2vowel.lastIndex// => 0: lastIndex gets reset after failed match
// One or more Unicode alphabetic characters between word boundariesconstwords=/\b\p{Alphabetic}+\b/gu; // \p is not supported in Firefox yetconsttext="This is a naïve test of the matchAll() method.";for(letwordoftext.matchAll(words)){console.log(`Found '${word[0]}' at index${word.index}.`);}
"123,456,789".split(",")// => ["123", "456", "789"]
"1, 2, 3,\n4, 5".split(/\s*,\s*/)// => ["1", "2", "3", "4", "5"]
consthtmlTag=/<([^>]+)>/;// < followed by one or more non->, followed by >"Testing<br/>1,2,3".split(htmlTag)// => ["Testing", "br/", "1,2,3"]
// Find all five-digit numbers in a string. Note the double \\ in this case.letzipcode=newRegExp("\\d{5}","g");
letexactMatch=/JavaScript/;letcaseInsensitive=newRegExp(exactMatch,"i");
sourceThis read-only property is the source text of the regular expression: the characters that appear between the slashes in a RegExp literal.
flagsThis read-only property is a string that specifies the set of letters that represent the flags for the RegExp.
globalA read-only boolean property that is true if the g flag
is set.
ignoreCaseA read-only boolean property that is true if the i
flag is set.
multilineA read-only boolean property that is true if the m
flag is set.
dotAllA read-only boolean property that is true if the s
flag is set.
unicodeA read-only boolean property that is true if the u
flag is set.
stickyA read-only boolean property that is true if the y
flag is set.
lastIndexThis property is a read/write integer. For patterns with
the g or y flags, it specifies the character position at which the
next search is to begin. It is used by the exec() and test()
methods, described in the next two subsections.
letpattern=/Java/g;lettext="JavaScript > Java";letmatch;while((match=pattern.exec(text))!==null){console.log(`Matched${match[0]}at${match.index}`);console.log(`Next search begins at${pattern.lastIndex}`);}
letnow=newDate();// The current time
letepoch=newDate(0);// Midnight, January 1st, 1970, GMT
letcentury=newDate(2100,// Year 21000,// January1,// 1st2,3,4,5);// 02:03:04.005, local time
// Midnight in England, January 1, 2100letcentury=newDate(Date.UTC(2100,0,1));
letcentury=newDate("2100-01-01T00:00:00Z");// An ISO format date
letd=newDate();// Start with the current dated.setFullYear(d.getFullYear()+1);// Increment the year
d.setTime(d.getTime()+30000);
letstartTime=Date.now();reticulateSplines();// Do some time-consuming operationletendTime=Date.now();console.log(`Spline reticulation took${endTime-startTime}ms.`);
letd=newDate();d.setMonth(d.getMonth()+3,d.getDate()+14);
letd=newDate(2020,0,1,17,10,30);// 5:10:30pm on New Year's Day 2020d.toString()// => "Wed Jan 01 2020 17:10:30 GMT-0800 (Pacific Standard Time)"d.toUTCString()// => "Thu, 02 Jan 2020 01:10:30 GMT"d.toLocaleDateString()// => "1/1/2020": 'en-US' localed.toLocaleTimeString()// => "5:10:30 PM": 'en-US' localed.toISOString()// => "2020-01-02T01:10:30.000Z"
toString()This method uses the local time zone but does not format the date and time in a locale-aware way.
toUTCString()This method uses the UTC time zone but does not format the date in a locale-aware way.
toISOString()This method prints the date and time in the standard year-month-day hours:minutes:seconds.ms format of the ISO-8601 standard. The letter “T” separates the date portion of the output from the time portion of the output. The time is expressed in UTC, and this is indicated with the letter “Z” as the last letter of the output.
toLocaleString()This method uses the local time zone and a format that is appropriate for the user’s locale.
toDateString()This method formats only the date portion of the Date and omits the time. It uses the local time zone and does not do locale-appropriate formatting.
toLocaleDateString()This method formats only the date. It uses the local time zone and a locale-appropriate date format.
toTimeString()This method formats only the time and omits the date. It uses the local time zone but does not format the time in a locale-aware way.
toLocaleTimeString()This method formats the time in a locale-aware way and uses the local time zone.
classHTTPErrorextendsError{constructor(status,statusText,url){super(`${status}${statusText}:${url}`);this.status=status;this.statusText=statusText;this.url=url;}getname(){return"HTTPError";}}leterror=newHTTPError(404,"Not Found","http://example.com/");error.status// => 404error.message// => "404 Not Found: http://example.com/"error.name// => "HTTPError"
leto={s:"",n:0,a:[true,false,null]};lets=JSON.stringify(o);// s == '{"s":"","n":0,"a":[true,false,null]}'letcopy=JSON.parse(s);// copy == {s: "", n: 0, a: [true, false, null]}
// Make a deep copy of any serializable object or arrayfunctiondeepcopy(o){returnJSON.parse(JSON.stringify(o));}
leto={s:"test",n:0};JSON.stringify(o,null,2)// => '{\n "s": "test",\n "n": 0\n}'
letdata=JSON.parse(text,function(key,value){// Remove any values whose property name begins with an underscoreif(key[0]==="_")returnundefined;// If the value is a string in ISO 8601 date format convert it to a Date.if(typeofvalue==="string"&&/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ$/.test(value)){returnnewDate(value);}// Otherwise, return the value unchangedreturnvalue;});
// Specify what fields to serialize, and what order to serialize them inlettext=JSON.stringify(address,["city","state","country"]);// Specify a replacer function that omits RegExp-value propertiesletjson=JSON.stringify(o,(k,v)=>vinstanceofRegExp?undefined:v);
styleSpecifies the kind of number formatting that is
required. The default is "decimal". Specify "percent" to format a
number as a percentage or specify "currency" to specify a number as
an amount of money.
currencyIf style is "currency", then this property is required
to specify the three-letter ISO currency code (such as "USD" for US
dollars or "GBP" for British pounds) of the desired currency.
currencyDisplayIf style is "currency", then this property
specifies how the currency is displayed. The default value "symbol"
uses a currency symbol if the currency has one. The value "code"
uses the three-letter ISO code, and the value "name" spells out the name
of the currency in long form.
useGroupingSet this property to false if you do not want
numbers to have thousands separators (or their locale-appropriate
equivalents).
minimumIntegerDigitsThe minimum number of digits to use to display the integer part of the number. If the number has fewer digits than this, it will be padded on the left with zeros. The default value is 1, but you can use values as high as 21.
minimumFractionDigits, maximumFractionDigitsThese two properties control the formatting of the fractional part of the number. If a number has fewer fractional digits than the minimum, it will be padded with zeros on the right. If it has more than the maximum, then the fractional part will be rounded. Legal values for both properties are between 0 and 20. The default minimum is 0 and the default maximum is 3, except when formatting monetary amounts, when the length of the fractional part varies depending on the specified currency.
minimumSignificantDigits, maximumSignificantDigitsThese properties control the number of significant digits used when formatting a number, making them suitable when formatting scientific data, for example. If specified, these properties override the integer and fractional digit properties listed previously. Legal values are between 1 and 21.
leteuros=Intl.NumberFormat("es",{style:"currency",currency:"EUR"});euros.format(10)// => "10,00 €": ten euros, Spanish formattingletpounds=Intl.NumberFormat("en",{style:"currency",currency:"GBP"});pounds.format(1000)// => "£1,000.00": One thousand pounds, English formatting
letdata=[0.05,.75,1];letformatData=Intl.NumberFormat(undefined,{style:"percent",minimumFractionDigits:1,maximumFractionDigits:1}).format;data.map(formatData)// => ["5.0%", "75.0%", "100.0%"]: in en-US locale
letarabic=Intl.NumberFormat("ar",{useGrouping:false}).format;arabic(1234567890)// => "١٢٣٤٥٦٧٨٩٠"
lethindi=Intl.NumberFormat("hi-IN-u-nu-deva").format;hindi(1234567890)// => "१,२३,४५,६७,८९०"
yearUse "numeric" for a full, four-digit year or "2-digit"
for a two-digit abbreviation.
monthUse "numeric" for a possibly short number like “1”, or
"2-digit" for a numeric representation that always has two digits,
like “01”. Use "long" for a full name like “January”, "short" for
an abbreviated name like “Jan”, and "narrow" for a highly
abbreviated name like “J” that is not guaranteed to be unique.
dayUse "numeric" for a one- or two-digit number or
"2-digit" for a two-digit number for the day-of-month.
weekdayUse "long" for a full name like “Monday”, "short"
for an abbreviated name like “Mon”, and "narrow" for a highly
abbreviated name like “M” that is not guaranteed to be unique.
eraThis property specifies whether a date should be formatted
with an era, such as CE or BCE. This may be useful if you are
formatting dates from very long ago or if you are using a Japanese
calendar. Legal values are "long", "short", and "narrow".
hour, minute, secondThese properties specify how you would
like time displayed. Use "numeric" for a one- or two-digit field or
"2-digit" to force single-digit numbers to be padded on the left
with a 0.
timeZoneThis property specifies the desired time zone for which the date should be formatted. If omitted, the local time zone is used. Implementations always recognize “UTC” and may also recognize Internet Assigned Numbers Authority (IANA) time zone names, such as “America/Los_Angeles”.
timeZoneNameThis property specifies how the time zone should be
displayed in a formatted date or time. Use "long" for a fully
spelled-out time zone name and "short" for an abbreviated or numeric
time zone.
hour12This boolean property specifies whether or not to use 12-hour time. The default is locale dependent, but you can override it with this property.
hourCycleThis property allows you to specify whether midnight is
written as 0 hours, 12 hours, or 24 hours. The default is
locale dependent, but you can override the default with this
property. Note that hour12 takes precedence over this property. Use
the value "h11" to specify that midnight is 0 and the hour before
midnight is 11pm. Use "h12" to specify that midnight is 12. Use
"h23" to specify that midnight is 0 and the hour before midnight is 23.
And use "h24" to specify that midnight is 24.
letd=newDate("2020-01-02T13:14:15Z");// January 2nd, 2020, 13:14:15 UTC// With no options, we get a basic numeric date formatIntl.DateTimeFormat("en-US").format(d)// => "1/2/2020"Intl.DateTimeFormat("fr-FR").format(d)// => "02/01/2020"// Spelled out weekday and monthletopts={weekday:"long",month:"long",year:"numeric",day:"numeric"};Intl.DateTimeFormat("en-US",opts).format(d)// => "Thursday, January 2, 2020"Intl.DateTimeFormat("es-ES",opts).format(d)// => "jueves, 2 de enero de 2020"// The time in New York, for a French-speaking Canadianopts={hour:"numeric",minute:"2-digit",timeZone:"America/New_York"};Intl.DateTimeFormat("fr-CA",opts).format(d)// => "8 h 14"
letopts={year:"numeric",era:"short"};Intl.DateTimeFormat("en",opts).format(d)// => "2020 AD"Intl.DateTimeFormat("en-u-ca-iso8601",opts).format(d)// => "2020 AD"Intl.DateTimeFormat("en-u-ca-hebrew",opts).format(d)// => "5780 AM"Intl.DateTimeFormat("en-u-ca-buddhist",opts).format(d)// => "2563 BE"Intl.DateTimeFormat("en-u-ca-islamic",opts).format(d)// => "1441 AH"Intl.DateTimeFormat("en-u-ca-persian",opts).format(d)// => "1398 AP"Intl.DateTimeFormat("en-u-ca-indian",opts).format(d)// => "1941 Saka"Intl.DateTimeFormat("en-u-ca-chinese",opts).format(d)// => "36 78"Intl.DateTimeFormat("en-u-ca-japanese",opts).format(d)// => "2 Reiwa"
usageThis property specifies how the collator object is to be
used. The default value is "sort", but you can also specify
"search". The idea is that, when sorting strings, you typically want a
collator that differentiates as many strings as possible to produce a
reliable ordering. But when comparing two strings, some locales may
want a less strict comparison that ignores accents, for example.
sensitivityThis property specifies whether the collator is
sensitive to letter case and accents when comparing strings. The value
"base" causes comparisons that ignore case and accents, considering
only the base letter for each character. (Note, however, that some
languages consider certain accented characters to be distinct base
letters.) "accent" considers accents in comparisons but ignores
case. "case" considers case and ignores accents. And "variant"
performs strict comparisons that consider both case and accents. The
default value for this property is "variant" when usage is
"sort". If usage is "search", then the default sensitivity
depends on the locale.
ignorePunctuationSet this property to true to ignore spaces and
punctuation when comparing strings. With this property set to true,
the strings “any one” and “anyone”, for example, will be considered
equal.
numericSet this property to true if the strings you are
comparing are integers or contain integers and you want them to be
sorted into numerical order instead of alphabetical order. With this
option set, the string “Version 9” will be sorted before “Version 10”,
for example.
caseFirstThis property specifies which letter case should come
first. If you specify "upper", then “A” will sort before “a”. And if
you specify "lower", then “a” will sort before “A”. In either case,
note that the upper- and lowercase variants of the same letter will be
next to one another in sort order, which is different than Unicode
lexicographic ordering (the default behavior of the Array sort()
method) in which all ASCII uppercase letters come before all ASCII
lowercase letters. The default for this property is locale dependent,
and implementations may ignore this property and not allow you to
override the case sort order.
// A basic comparator for sorting in the user's locale.// Never sort human-readable strings without passing something like this:constcollator=newIntl.Collator().compare;["a","z","A","Z"].sort(collator)// => ["a", "A", "z", "Z"]// Filenames often include numbers, so we should sort those speciallyconstfilenameOrder=newIntl.Collator(undefined,{numeric:true}).compare;["page10","page9"].sort(filenameOrder)// => ["page9", "page10"]// Find all strings that loosely match a target stringconstfuzzyMatcher=newIntl.Collator(undefined,{sensitivity:"base",ignorePunctuation:true}).compare;letstrings=["food","fool","Føø Bar"];strings.findIndex(s=>fuzzyMatcher(s,"foobar")===0)// => 2
// Before 1994, CH and LL were treated as separate letters in SpainconstmodernSpanish=Intl.Collator("es-ES").compare;consttraditionalSpanish=Intl.Collator("es-ES-u-co-trad").compare;letpalabras=["luz","llama","como","chico"];palabras.sort(modernSpanish)// => ["chico", "como", "llama", "luz"]palabras.sort(traditionalSpanish)// => ["como", "chico", "luz", "llama"]
console.log()This is the most well-known of the console functions. It converts its arguments to strings and outputs them to the console. It includes spaces between the arguments and starts a new line after outputting all arguments.
console.debug(), console.info(), console.warn(), console.error()These functions are almost identical to console.log(). In Node,
console.error() sends its output to the stderr stream rather than
the stdout stream, but the other functions are aliases of
console.log(). In browsers, output messages generated by each of
these functions may be prefixed by an icon that indicates its level
or severity, and the developer console may also allow developers to
filter console messages by level.
console.assert()If the first argument is truthy (i.e., if the
assertion passes), then this function does nothing. But if the first
argument is false or another falsy value, then the remaining
arguments are printed as if they had been passed to console.error()
with an “Assertion failed” prefix. Note that, unlike typical
assert() functions, console.assert() does not throw an exception
when an assertion fails.
console.clear()This function clears the console when that is possible. This works in browsers and in Node when Node is displaying its output to a terminal. If Node’s output has been redirected to a file or a pipe, however, then calling this function has no effect.
console.table()This function is a remarkably powerful but
little-known feature for producing tabular output, and it is
particularly useful in Node programs that need to produce output that
summarizes data. console.table() attempts to display its argument in
tabular form (although, if it can’t do that, it displays it using regular
console.log() formatting). This works best when the argument is a
relatively short array of objects, and all of the objects in the array
have the same (relatively small) set of properties. In this case,
each object in the array is formatted as a row of the table, and each
property is a column of the table. You can also pass an array of
property names as an optional second argument to specify the desired
set of columns. If you pass an object instead of an array of objects,
then the output will be a table with one column for property names and
one column for property values. Or, if those property values are
themselves objects, their property names will become columns in the
table.
console.trace()This function logs its arguments like
console.log() does, and, in addition, follows its output with a
stack trace. In Node, the output goes to stderr instead of stdout.
console.count()This function takes a string argument and logs that string, followed by the number of times it has been called with that string. This can be useful when debugging an event handler, for example, if you need to keep track of how many times the event handler has been triggered.
console.countReset()This function takes a string argument and resets the counter for that string.
console.group()This function prints its arguments to the console
as if they had been passed to console.log(), then sets the
internal state of the console so that all subsequent console messages
(until the next console.groupEnd() call) will be indented relative
to the message that it just printed. This allows a group of related
messages to be visually grouped with indentation. In web browsers, the
developer console typically allows grouped messages to be collapsed
and expanded as a group. The arguments to console.group() are
typically used to provide an explanatory name for the group.
console.groupCollapsed()This function works like
console.group() except that in web browsers, the group will be
“collapsed” by default and the messages it contains will be hidden
unless the user clicks to expand the group. In Node, this function is
a synonym for console.group().
console.groupEnd()This function takes no arguments. It produces
no output of its own but ends the indentation and grouping caused by
the most recent call to console.group() or
console.groupCollapsed().
console.time()This function takes a single string argument, makes a note of the time it was called with that string, and produces no output.
console.timeLog()This function takes a string as its first
argument. If that string had previously been passed to
console.time(), then it prints that string followed by the elapsed
time since the console.time() call. If there are any additional
arguments to console.timeLog(), they are printed as if they had been
passed to console.log().
console.timeEnd()This function takes a single string argument. If
that argument had previously been passed to console.time(), then it
prints that argument and the elapsed time. After calling
console.timeEnd(), it is no longer legal to call console.timeLog()
without first calling console.time() again.
%sThe argument is converted to a string.
%i and %dThe argument is converted to a number and then truncated to an integer.
%fThe argument is converted to a number
%o and %OThe argument is treated as an
object, and property names and values are displayed. (In web browsers,
this display is typically interactive, and users can expand and
collapse properties to explore a nested data structure.) %o and %O
both display object details. The uppercase variant uses an
implementation-dependent output format that is judged to be most
useful for software developers.
%cIn web browsers, the argument is interpreted as a string of CSS
styles and used to style any text that follows (until the next %c
sequence or the end of the string). In Node, the %c sequence and its
corresponding argument are simply ignored.
leturl=newURL("https://example.com:8000/path/name?q=term#fragment");url.href// => "https://example.com:8000/path/name?q=term#fragment"url.origin// => "https://example.com:8000"url.protocol// => "https:"url.host// => "example.com:8000"url.hostname// => "example.com"url.port// => "8000"url.pathname// => "/path/name"url.search// => "?q=term"url.hash// => "#fragment"
leturl=newURL("ftp://admin:1337!@ftp.example.com/");url.href// => "ftp://admin:1337!@ftp.example.com/"url.origin// => "ftp://ftp.example.com"url.username// => "admin"url.password// => "1337!"
leturl=newURL("https://example.com");// Start with our serverurl.pathname="api/search";// Add a path to an API endpointurl.search="q=test";// Add a query parameterurl.toString()// => "https://example.com/api/search?q=test"
leturl=newURL("https://example.com");url.pathname="path with spaces";url.search="q=foo#bar";url.pathname// => "/path%20with%20spaces"url.search// => "?q=foo%23bar"url.href// => "https://example.com/path%20with%20spaces?q=foo%23bar"
leturl=newURL("https://example.com/search");url.search// => "": no query yeturl.searchParams.append("q","term");// Add a search parameterurl.search// => "?q=term"url.searchParams.set("q","x");// Change the value of this parameterurl.search// => "?q=x"url.searchParams.get("q")// => "x": query the parameter valueurl.searchParams.has("q")// => true: there is a q parameterurl.searchParams.has("p")// => false: there is no p parameterurl.searchParams.append("opts","1");// Add another search parameterurl.search// => "?q=x&opts=1"url.searchParams.append("opts","&");// Add another value for same nameurl.search// => "?q=x&opts=1&opts=%26": note escapeurl.searchParams.get("opts")// => "1": the first valueurl.searchParams.getAll("opts")// => ["1", "&"]: all valuesurl.searchParams.sort();// Put params in alphabetical orderurl.search// => "?opts=1&opts=%26&q=x"url.searchParams.set("opts","y");// Change the opts paramurl.search// => "?opts=y&q=x"// searchParams is iterable[...url.searchParams]// => [["opts", "y"], ["q", "x"]]url.searchParams.delete("opts");// Delete the opts paramurl.search// => "?q=x"url.href// => "https://example.com/search?q=x"
leturl=newURL("http://example.com");letparams=newURLSearchParams();params.append("q","term");params.append("opts","exact");params.toString()// => "q=term&opts=exact"url.search=params;url.href// => "http://example.com/?q=term&opts=exact"
encodeURI() and decodeURI()encodeURI() takes a string as its
argument and returns a new string in which non-ASCII characters plus
certain ASCII characters (such as space) are escaped. decodeURI()
reverses the process. Characters that need to be escaped are first
converted to their UTF-8 encoding, then each byte of that encoding
is replaced with a %xx escape sequence, where xx is two
hexadecimal digits. Because encodeURI() is intended for encoding
entire URLs, it does not escape URL separator characters such as /,
?, and #. But this means that encodeURI() cannot work correctly
for URLs that have those characters within their various components.
encodeURIComponent() and decodeURIComponent()This pair of
functions works just like encodeURI() and decodeURI() except that
they are intended to escape individual components of a URI, so they
also escape characters like /, ?, and # that are used to separate
those components. These are the most useful of the legacy URL
functions, but be aware that encodeURIComponent() will escape /
characters in a path name that you probably do not want escaped. And
it will convert spaces in a query parameter to %20, even though
spaces are supposed to be escaped with a + in that portion of a
URL.
setTimeout(()=>{console.log("Ready...");},1000);setTimeout(()=>{console.log("set...");},2000);setTimeout(()=>{console.log("go!");},3000);
// Once a second: clear the console and print the current timeletclock=setInterval(()=>{console.clear();console.log(newDate().toLocaleTimeString());},1000);// After 10 seconds: stop the repeating code above.setTimeout(()=>{clearInterval(clock);},10000);
Important data structures, such as Set, Map, and typed arrays.
The Date and URL classes for working with dates and URLs.
JavaScript’s regular expression grammar and its RegExp class for textual pattern matching.
JavaScript’s internationalization library for formatting dates, time, and numbers and for sorting strings.
The JSON object for serializing and deserializing simple data
structures and the console object for logging messages.
1 Not everything documented here is defined by the JavaScript language specification: some of the classes and functions documented here were first implemented in web browsers and then adopted by Node, making them de facto members of the JavaScript standard library.
2 This predictable iteration order is another thing about JavaScript sets that Python programmers may find surprising.
3 Typed arrays were first introduced to client-side JavaScript when web browsers added support for WebGL graphics. What is new in ES6 is that they have been elevated to a core language feature.
4 Except within a character class (square brackets), where \b matches the backspace character.
5 Parsing URLs with regular expressions is not a good idea. See §11.9 for a more robust URL parser.
6 C programmers will recognize many of these character sequences from the printf() function.
letsum=0;for(letiof[1,2,3]){// Loop once for each of these valuessum+=i;}sum// => 6
letchars=[..."abcd"];// chars == ["a", "b", "c", "d"]letdata=[1,2,3,4,5];Math.max(...data)// => 5
letpurpleHaze=Uint8Array.of(255,0,255,128);let[r,g,b,a]=purpleHaze;// a == 128
letm=newMap([["one",1],["two",2]]);for(let[k,v]ofm)console.log(k,v);// Logs 'one 1' and 'two 2'
[...m]// => [["one", 1], ["two", 2]]: default iteration[...m.entries()]// => [["one", 1], ["two", 2]]: entries() method is the same[...m.keys()]// => ["one", "two"]: keys() method iterates just map keys[...m.values()]// => [1, 2]: values() method iterates just map values
// Strings are iterable, so the two sets are the same:newSet("abc")// => new Set(["a", "b", "c"])
letiterable=[99];letiterator=iterable[Symbol.iterator]();for(letresult=iterator.next();!result.done;result=iterator.next()){console.log(result.value)// result.value == 99}
letlist=[1,2,3,4,5];letiter=list[Symbol.iterator]();lethead=iter.next().value;// head == 1lettail=[...iter];// tail == [2,3,4,5]
/** A Range object represents a range of numbers {x: from <= x <= to}* Range defines a has() method for testing whether a given number is a member* of the range. Range is iterable and iterates all integers within the range.*/classRange{constructor(from,to){this.from=from;this.to=to;}// Make a Range act like a Set of numbershas(x){returntypeofx==="number"&&this.from<=x&&x<=this.to;}// Return string representation of the range using set notationtoString(){return`{ x |${this.from}≤ x ≤${this.to}}`;}// Make a Range iterable by returning an iterator object.// Note that the name of this method is a special symbol, not a string.[Symbol.iterator](){// Each iterator instance must iterate the range independently of// others. So we need a state variable to track our location in the// iteration. We start at the first integer >= from.letnext=Math.ceil(this.from);// This is the next value we returnletlast=this.to;// We won't return anything > thisreturn{// This is the iterator object// This next() method is what makes this an iterator object.// It must return an iterator result object.next(){return(next<=last)// If we haven't returned last value yet?{value:next++}// return next value and increment it:{done:true};// otherwise indicate that we're done.},// As a convenience, we make the iterator itself iterable.[Symbol.iterator](){returnthis;}};}}for(letxofnewRange(1,10))console.log(x);// Logs numbers 1 to 10[...newRange(-2,2)]// => [-2, -1, 0, 1, 2]
// Return an iterable object that iterates the result of applying f()// to each value from the source iterablefunctionmap(iterable,f){letiterator=iterable[Symbol.iterator]();return{// This object is both iterator and iterable[Symbol.iterator](){returnthis;},next(){letv=iterator.next();if(v.done){returnv;}else{return{value:f(v.value)};}}};}// Map a range of integers to their squares and convert to an array[...map(newRange(1,4),x=>x*x)]// => [1, 4, 9, 16]// Return an iterable object that filters the specified iterable,// iterating only those elements for which the predicate returns truefunctionfilter(iterable,predicate){letiterator=iterable[Symbol.iterator]();return{// This object is both iterator and iterable[Symbol.iterator](){returnthis;},next(){for(;;){letv=iterator.next();if(v.done||predicate(v.value)){returnv;}}}};}// Filter a range so we're left with only even numbers[...filter(newRange(1,10),x=>x%2===0)]// => [2,4,6,8,10]
functionwords(s){varr=/\s+|$/g;// Match one or more spaces or endr.lastIndex=s.match(/[^ ]/).index;// Start matching at first nonspacereturn{// Return an iterable iterator object[Symbol.iterator](){// This makes us iterablereturnthis;},next(){// This makes us an iteratorletstart=r.lastIndex;// Resume where the last match endedif(start<s.length){// If we're not doneletmatch=r.exec(s);// Match the next word boundaryif(match){// If we found one, return the wordreturn{value:s.substring(start,match.index)};}}return{done:true};// Otherwise, say that we're done}};}[...words(" abc def ghi! ")]// => ["abc", "def", "ghi!"]
// A generator function that yields the set of one digit (base-10) primes.function*oneDigitPrimes(){// Invoking this function does not run the codeyield2;// but just returns a generator object. Callingyield3;// the next() method of that generator runsyield5;// the code until a yield statement providesyield7;// the return value for the next() method.}// When we invoke the generator function, we get a generatorletprimes=oneDigitPrimes();// A generator is an iterator object that iterates the yielded valuesprimes.next().value// => 2primes.next().value// => 3primes.next().value// => 5primes.next().value// => 7primes.next().done// => true// Generators have a Symbol.iterator method to make them iterableprimes[Symbol.iterator]()// => primes// We can use generators like other iterable types[...oneDigitPrimes()]// => [2,3,5,7]letsum=0;for(letprimeofoneDigitPrimes())sum+=prime;sum// => 17
constseq=function*(from,to){for(leti=from;i<=to;i++)yieldi;};[...seq(3,5)]// => [3, 4, 5]
leto={x:1,y:2,z:3,// A generator that yields each of the keys of this object*g(){for(letkeyofObject.keys(this)){yieldkey;}}};[...o.g()]// => ["x", "y", "z", "g"]
*[Symbol.iterator](){for(letx=Math.ceil(this.from);x<=this.to;x++)yieldx;}
function*fibonacciSequence(){letx=0,y=1;for(;;){yieldy;[x,y]=[y,x+y];// Note: destructuring assignment}}
// Return the nth Fibonacci numberfunctionfibonacci(n){for(letfoffibonacciSequence()){if(n--<=0)returnf;}}fibonacci(20)// => 10946
// Yield the first n elements of the specified iterable objectfunction*take(n,iterable){letit=iterable[Symbol.iterator]();// Get iterator for iterable objectwhile(n-->0){// Loop n times:letnext=it.next();// Get the next item from the iterator.if(next.done)return;// If there are no more values, return earlyelseyieldnext.value;// otherwise, yield the value}}// An array of the first 5 Fibonacci numbers[...take(5,fibonacciSequence())]// => [1, 1, 2, 3, 5]
// Given an array of iterables, yield their elements in interleaved order.function*zip(...iterables){// Get an iterator for each iterableletiterators=iterables.map(i=>i[Symbol.iterator]());letindex=0;while(iterators.length>0){// While there are still some iteratorsif(index>=iterators.length){// If we reached the last iteratorindex=0;// go back to the first one.}letitem=iterators[index].next();// Get next item from next iterator.if(item.done){// If that iterator is doneiterators.splice(index,1);// then remove it from the array.}else{// Otherwise,yielditem.value;// yield the iterated valueindex++;// and move on to the next iterator.}}}// Interleave three iterable objects[...zip(oneDigitPrimes(),"ab",[0])]// => [2,"a",0,3,"b",5,7]
function*sequence(...iterables){for(letiterableofiterables){for(letitemofiterable){yielditem;}}}[...sequence("abc",oneDigitPrimes())]// => ["a","b","c",2,3,5,7]
function*sequence(...iterables){for(letiterableofiterables){yield*iterable;}}[...sequence("abc",oneDigitPrimes())]// => ["a","b","c",2,3,5,7]
function*sequence(...iterables){iterables.forEach(iterable=>yield*iterable);// Error}
function*oneAndDone(){yield1;return"done";}// The return value does not appear in normal iteration.[...oneAndDone()]// => [1]// But it is available if you explicitly call next()letgenerator=oneAndDone();generator.next()// => { value: 1, done: false}generator.next()// => { value: "done", done: true }// If the generator is already done, the return value is not returned againgenerator.next()// => { value: undefined, done: true }
function*smallNumbers(){console.log("next() invoked the first time; argument discarded");lety1=yield1;// y1 == "b"console.log("next() invoked a second time with argument",y1);lety2=yield2;// y2 == "c"console.log("next() invoked a third time with argument",y2);lety3=yield3;// y3 == "d"console.log("next() invoked a fourth time with argument",y3);return4;}letg=smallNumbers();console.log("generator created; no code runs yet");letn1=g.next("a");// n1.value == 1console.log("generator yielded",n1.value);letn2=g.next("b");// n2.value == 2console.log("generator yielded",n2.value);letn3=g.next("c");// n3.value == 3console.log("generator yielded",n3.value);letn4=g.next("d");// n4 == { value: 4, done: true }console.log("generator returned",n4.value);
generator created; no code runs yet next() invoked the first time; argument discarded generator yielded 1 next() invoked a second time with argument b generator yielded 2 next() invoked a third time with argument c generator yielded 3 next() invoked a fourth time with argument d generator returned 4
The for/of loop and the ... spread operator work with iterable
objects.
An object is iterable if it has a method with the symbolic name
[Symbol.iterator] that returns an iterator object.
An iterator object has a next() method that returns an iteration
result object.
An iteration result object has a value property that holds the
next iterated value, if there is one. If the iteration has
completed, then the result object must have a done property
set to true.
You can implement your own iterable objects by defining a
[Symbol.iterator]() method that returns an object with a next()
method that returns iteration result objects. You can also implement
functions that accept iterator arguments and return iterator
values.
Generator functions (functions defined with function* instead of
function) are another way to define iterators.
When you invoke a generator function, the body of the function does
not run right away; instead, the return value is an iterable iterator
object. Each time the next() method of the iterator is called,
another chunk of the generator function runs.
Generator functions can use the yield operator to specify the
values that are returned by the iterator. Each call to next()
causes the generator function to run up to the next yield
expression. The value of that yield expression then becomes the value
returned by the iterator. When there are no more yield
expressions, then the generator function returns, and the iteration
is complete.
setTimeout(checkForUpdates,60000);
// Call checkForUpdates in one minute and then again every minute after thatletupdateIntervalId=setInterval(checkForUpdates,60000);// setInterval() returns a value that we can use to stop the repeated// invocations by calling clearInterval(). (Similarly, setTimeout()// returns a value that you can pass to clearTimeout())functionstopCheckingForUpdates(){clearInterval(updateIntervalId);}
// Ask the web browser to return an object representing the HTML// <button> element that matches this CSS selectorletokay=document.querySelector('#confirmUpdateDialog button.okay');// Now register a callback function to be invoked when the user// clicks on that button.okay.addEventListener('click',applyUpdate);
functiongetCurrentVersionNumber(versionCallback){// Note callback argument// Make a scripted HTTP request to a backend version APIletrequest=newXMLHttpRequest();request.open("GET","http://www.example.com/api/version");request.send();// Register a callback that will be invoked when the response arrivesrequest.onload=function(){if(request.status===200){// If HTTP status is good, get version number and call callback.letcurrentVersion=parseFloat(request.responseText);versionCallback(null,currentVersion);}else{// Otherwise report an error to the callbackversionCallback(response.statusText,null);}};// Register another callback that will be invoked for network errorsrequest.onerror=request.ontimeout=function(e){versionCallback(e.type,null);};}
constfs=require("fs");// The "fs" module has filesystem-related APIsletoptions={// An object to hold options for our program// default options would go here};// Read a configuration file, then call the callback functionfs.readFile("config.json","utf-8",(err,text)=>{if(err){// If there was an error, display a warning, but continueconsole.warn("Could not read config file:",err);}else{// Otherwise, parse the file contents and assign to the options objectObject.assign(options,JSON.parse(text));}// In either case, we can now start running the programstartProgram(options);});
consthttps=require("https");// Read the text content of the URL and asynchronously pass it to the callback.functiongetText(url,callback){// Start an HTTP GET request for the URLrequest=https.get(url);// Register a function to handle the "response" event.request.on("response",response=>{// The response event means that response headers have been receivedlethttpStatus=response.statusCode;// The body of the HTTP response has not been received yet.// So we register more event handlers to to be called when it arrives.response.setEncoding("utf-8");// We're expecting Unicode textletbody="";// which we will accumulate here.// This event handler is called when a chunk of the body is readyresponse.on("data",chunk=>{body+=chunk;});// This event handler is called when the response is completeresponse.on("end",()=>{if(httpStatus===200){// If the HTTP response was goodcallback(null,body);// Pass response body to the callback}else{// Otherwise pass an errorcallback(httpStatus,null);}});});// We also register an event handler for lower-level network errorsrequest.on("error",(err)=>{callback(err,null);});}
Explain Promise terminology and show basic Promise usage
Show how promises can be chained
Demonstrate how to create your own Promise-based APIs
getJSON(url).then(jsonData=>{// This is a callback function that will be asynchronously// invoked with the parsed JSON value when it becomes available.});
// Suppose you have a function like this to display a user profilefunctiondisplayUserProfile(profile){/* implementation omitted */}// Here's how you might use that function with a Promise.// Notice how this line of code reads almost like an English sentence:getJSON("/api/user/profile").then(displayUserProfile);
getJSON("/api/user/profile").then(displayUserProfile,handleProfileError);
getJSON("/api/user/profile").then(displayUserProfile).catch(handleProfileError);
fetch(documentURL)// Make an HTTP request.then(response=>response.json())// Ask for the JSON body of the response.then(document=>{// When we get the parsed JSONreturnrender(document);// display the document to the user}).then(rendered=>{// When we get the rendered documentcacheInDatabase(rendered);// cache it in the local database.}).catch(error=>handle(error));// Handle any errors that occur
fetch("/api/user/profile").then(response=>{// When the promise resolves, we have status and headersif(response.ok&&response.headers.get("Content-Type")==="application/json"){// What can we do here? We don't actually have the response body yet.}});
fetch("/api/user/profile").then(response=>{response.json().then(profile=>{// Ask for the JSON-parsed body// When the body of the response arrives, it will be automatically// parsed as JSON and passed to this function.displayUserProfile(profile);});});
fetch("/api/user/profile").then(response=>{returnresponse.json();}).then(profile=>{displayUserProfile(profile);});
fetch().then().then()
fetch(theURL)// task 1; returns promise 1.then(callback1)// task 2; returns promise 2.then(callback2);// task 3; returns promise 3
On the first line, fetch() is invoked with a URL. It initiates an
HTTP GET request for that URL and returns a Promise. We’ll call this
HTTP request “task 1” and we’ll call the Promise “promise 1”.
On the second line, we invoke the then() method of promise 1,
passing the callback1 function that we want to be invoked when
promise 1 is fulfilled. The then() method stores our callback
somewhere, then returns a new Promise. We’ll call the new Promise
returned at this step “promise 2”, and we’ll say that “task 2” begins
when callback1 is invoked.
On the third line, we invoke the then() method of promise 2,
passing the callback2 function we want invoked when promise 2 is
fulfilled. This then() method remembers our callback and returns yet
another Promise. We’ll say that “task 3” begins when callback2 is
invoked. We can call this latest Promise “promise 3”, but we don’t
really need a name for it because we won’t be using it at all.
The previous three steps all happen synchronously when the expression is first executed. Now we have an asynchronous pause while the HTTP request initiated in step 1 is sent out across the internet.
Eventually, the HTTP response starts to arrive. The asynchronous part
of the fetch() call wraps the HTTP status and headers in a Response
object and fulfills promise 1 with that Response object as the value.
When promise 1 is fulfilled, its value (the Response object) is
passed to our callback1() function, and task 2
begins. The job of this task, given a Response object as input, is to
obtain the response body as a JSON object.
Let’s assume that task 2 completes normally and is able to parse the body of the HTTP response to produce a JSON object. This JSON object is used to fulfill promise 2.
The value that fulfills promise 2 becomes the input to task 3 when
it is passed to the callback2() function. This third task now
displays the data to the user in some unspecified way. When task 3 is
complete (assuming it completes normally), then promise 3 will be
fulfilled. But because we never did anything with promise 3, nothing
happens when that Promise settles, and the chain of asynchronous
computation ends at this point.
functionc1(response){// callback 1letp4=response.json();returnp4;// returns promise 4}functionc2(profile){// callback 2displayUserProfile(profile);}letp1=fetch("/api/user/profile");// promise 1, task 1letp2=p1.then(c1);// promise 2, task 2letp3=p2.then(c2);// promise 3, task 3
p.then(null,c);p.catch(c);
fetch("/api/user/profile")// Start the HTTP request.then(response=>{// Call this when status and headers are readyif(!response.ok){// If we got a 404 Not Found or similar errorreturnnull;// Maybe user is logged out; return null profile}// Now check the headers to ensure that the server sent us JSON.// If not, our server is broken, and this is a serious error!lettype=response.headers.get("content-type");if(type!=="application/json"){thrownewTypeError(`Expected JSON, got${type}`);}// If we get here, then we got a 2xx status and a JSON content-type// so we can confidently return a Promise for the response// body as a JSON object.returnresponse.json();}).then(profile=>{// Called with the parsed response body or nullif(profile){displayUserProfile(profile);}else{// If we got a 404 error above and returned null we end up heredisplayLoggedOutProfilePage();}}).catch(e=>{if(einstanceofNetworkError){// fetch() can fail this way if the internet connection is downdisplayErrorMessage("Check your internet connection.");}elseif(einstanceofTypeError){// This happens if we throw TypeError abovedisplayErrorMessage("Something is wrong with our server!");}else{// This must be some kind of unanticipated errorconsole.error(e);}});
startAsyncOperation().then(doStageTwo).catch(recoverFromStageTwoError).then(doStageThree).then(doStageFour).catch(logStageThreeAndFourErrors);
queryDatabase().then(displayTable).catch(displayDatabaseError);
queryDatabase().catch(e=>wait(500).then(queryDatabase))// On failure, wait and retry.then(displayTable).catch(displayDatabaseError);
// We start with an array of URLsconsturls=[/* zero or more URLs here */];// And convert it to an array of Promise objectspromises=urls.map(url=>fetch(url).then(r=>r.text()));// Now get a Promise to run all those Promises in parallelPromise.all(promises).then(bodies=>{/* do something with the array of strings */}).catch(e=>console.error(e));
Promise.allSettled([Promise.resolve(1),Promise.reject(2),3]).then(results=>{results[0]// => { status: "fulfilled", value: 1 }results[1]// => { status: "rejected", reason: 2 }results[2]// => { status: "fulfilled", value: 3 }});
functiongetJSON(url){returnfetch(url).then(response=>response.json());}
functiongetHighScore(){returngetJSON("/api/user/profile").then(profile=>profile.highScore);}
functionwait(duration){// Create and return a new PromisereturnnewPromise((resolve,reject)=>{// These control the Promise// If the argument is invalid, reject the Promiseif(duration<0){reject(newError("Time travel not yet implemented"));}// Otherwise, wait asynchronously and then resolve the Promise.// setTimeout will invoke resolve() with no arguments, which means// that the Promise will fulfill with the undefined value.setTimeout(resolve,duration);});}
consthttp=require("http");functiongetJSON(url){// Create and return a new PromisereturnnewPromise((resolve,reject)=>{// Start an HTTP GET request for the specified URLrequest=http.get(url,response=>{// called when response starts// Reject the Promise if the HTTP status is wrongif(response.statusCode!==200){reject(newError(`HTTP status${response.statusCode}`));response.resume();// so we don't leak memory}// And reject if the response headers are wrongelseif(response.headers["content-type"]!=="application/json"){reject(newError("Invalid content-type"));response.resume();// don't leak memory}else{// Otherwise, register events to read the body of the responseletbody="";response.setEncoding("utf-8");response.on("data",chunk=>{body+=chunk;});response.on("end",()=>{// When the response body is complete, try to parse ittry{letparsed=JSON.parse(body);// If it parsed successfully, fulfill the Promiseresolve(parsed);}catch(e){// If parsing failed, reject the Promisereject(e);}});}});// We also reject the Promise if the request fails before we// even get a response (such as when the network is down)request.on("error",error=>{reject(error);});});}
functionfetchSequentially(urls){// We'll store the URL bodies here as we fetch themconstbodies=[];// Here's a Promise-returning function that fetches one bodyfunctionfetchOne(url){returnfetch(url).then(response=>response.text()).then(body=>{// We save the body to the array, and we're purposely// omitting a return value here (returning undefined)bodies.push(body);});}// Start with a Promise that will fulfill right away (with value undefined)letp=Promise.resolve(undefined);// Now loop through the desired URLs, building a Promise chain// of arbitrary length, fetching one URL at each stage of the chainfor(urlofurls){p=p.then(()=>fetchOne(url));}// When the last Promise in that chain is fulfilled, then the// bodies array is ready. So let's return a Promise for that// bodies array. Note that we don't include any error handlers:// we want to allow errors to propagate to the caller.returnp.then(()=>bodies);}
fetchSequentially(urls).then(bodies=>{/* do something with the array of strings */}).catch(e=>console.error(e));
// This function takes an array of input values and a "promiseMaker" function.// For any input value x in the array, promiseMaker(x) should return a Promise// that will fulfill to an output value. This function returns a Promise// that fulfills to an array of the computed output values.//// Rather than creating the Promises all at once and letting them run in// parallel, however, promiseSequence() only runs one Promise at a time// and does not call promiseMaker() for a value until the previous Promise// has fulfilled.functionpromiseSequence(inputs,promiseMaker){// Make a private copy of the array that we can modifyinputs=[...inputs];// Here's the function that we'll use as a Promise callback// This is the pseudorecursive magic that makes this all work.functionhandleNextInput(outputs){if(inputs.length===0){// If there are no more inputs left, then return the array// of outputs, finally fulfilling this Promise and all the// previous resolved-but-not-fulfilled Promises.returnoutputs;}else{// If there are still input values to process, then we'll// return a Promise object, resolving the current Promise// with the future value from a new Promise.letnextInput=inputs.shift();// Get the next input value,returnpromiseMaker(nextInput)// compute the next output value,// Then create a new outputs array with the new output value.then(output=>outputs.concat(output))// Then "recurse", passing the new, longer, outputs array.then(handleNextInput);}}// Start with a Promise that fulfills to an empty array and use// the function above as its callback.returnPromise.resolve([]).then(handleNextInput);}
// Given a URL, return a Promise that fulfills to the URL body textfunctionfetchBody(url){returnfetch(url).then(r=>r.text());}// Use it to sequentially fetch a bunch of URL bodiespromiseSequence(urls,fetchBody).then(bodies=>{/* do something with the array of strings */}).catch(console.error);
letresponse=awaitfetch("/api/user/profile");letprofile=awaitresponse.json();
asyncfunctiongetHighScore(){letresponse=awaitfetch("/api/user/profile");letprofile=awaitresponse.json();returnprofile.highScore;}
displayHighScore(awaitgetHighScore());
getHighScore().then(displayHighScore).catch(console.error);
asyncfunctiongetJSON(url){letresponse=awaitfetch(url);letbody=awaitresponse.json();returnbody;}
letvalue1=awaitgetJSON(url1);letvalue2=awaitgetJSON(url2);
let[value1,value2]=awaitPromise.all([getJSON(url1),getJSON(url2)]);
asyncfunctionf(x){/* body */}
functionf(x){returnnewPromise(function(resolve,reject){try{resolve((function(x){/* body */})(x));}catch(e){reject(e);}});}
constfs=require("fs");asyncfunctionparseFile(filename){letstream=fs.createReadStream(filename,{encoding:"utf-8"});forawait(letchunkofstream){parseChunk(chunk);// Assume parseChunk() is defined elsewhere}}
consturls=[url1,url2,url3];
constpromises=urls.map(url=>fetch(url));
for(constpromiseofpromises){response=awaitpromise;handle(response);}
forawait(constresponseofpromises){handle(response);}
// A Promise-based wrapper around setTimeout() that we can use await with.// Returns a Promise that fulfills in the specified number of millisecondsfunctionelapsedTime(ms){returnnewPromise(resolve=>setTimeout(resolve,ms));}// An async generator function that increments a counter and yields it// a specified (or infinite) number of times at a specified interval.asyncfunction*clock(interval,max=Infinity){for(letcount=1;count<=max;count++){// regular for loopawaitelapsedTime(interval);// wait for time to passyieldcount;// yield the counter}}// A test function that uses the async generator with for/awaitasyncfunctiontest(){// Async so we can use for/awaitforawait(lettickofclock(300,100)){// Loop 100 times every 300msconsole.log(tick);}}
functionclock(interval,max=Infinity){// A Promise-ified version of setTimeout that we can use await with.// Note that this takes an absolute time instead of an interval.functionuntil(time){returnnewPromise(resolve=>setTimeout(resolve,time-Date.now()));}// Return an asynchronously iterable objectreturn{startTime:Date.now(),// Remember when we startedcount:1,// Remember which iteration we're onasyncnext(){// The next() method makes this an iteratorif(this.count>max){// Are we done?return{done:true};// Iteration result indicating done}// Figure out when the next iteration should begin,lettargetTime=this.startTime+this.count*interval;// wait until that time,awaituntil(targetTime);// and return the count value in an iteration result object.return{value:this.count++};},// This method means that this iterator object is also an iterable.[Symbol.asyncIterator](){returnthis;}};}
/*** An asynchronously iterable queue class. Add values with enqueue()* and remove them with dequeue(). dequeue() returns a Promise, which* means that values can be dequeued before they are enqueued. The* class implements [Symbol.asyncIterator] and next() so that it can* be used with the for/await loop (which will not terminate until* the close() method is called.)*/classAsyncQueue{constructor(){// Values that have been queued but not dequeued yet are stored herethis.values=[];// When Promises are dequeued before their corresponding values are// queued, the resolve methods for those Promises are stored here.this.resolvers=[];// Once closed, no more values can be enqueued, and no more unfulfilled// Promises returned.this.closed=false;}enqueue(value){if(this.closed){thrownewError("AsyncQueue closed");}if(this.resolvers.length>0){// If this value has already been promised, resolve that Promiseconstresolve=this.resolvers.shift();resolve(value);}else{// Otherwise, queue it upthis.values.push(value);}}dequeue(){if(this.values.length>0){// If there is a queued value, return a resolved Promise for itconstvalue=this.values.shift();returnPromise.resolve(value);}elseif(this.closed){// If no queued values and we're closed, return a resolved// Promise for the "end-of-stream" markerreturnPromise.resolve(AsyncQueue.EOS);}else{// Otherwise, return an unresolved Promise,// queuing the resolver function for later usereturnnewPromise((resolve)=>{this.resolvers.push(resolve);});}}close(){// Once the queue is closed, no more values will be enqueued.// So resolve any pending Promises with the end-of-stream markerwhile(this.resolvers.length>0){this.resolvers.shift()(AsyncQueue.EOS);}this.closed=true;}// Define the method that makes this class asynchronously iterable[Symbol.asyncIterator](){returnthis;}// Define the method that makes this an asynchronous iterator. The// dequeue() Promise resolves to a value or the EOS sentinel if we're// closed. Here, we need to return a Promise that resolves to an// iterator result object.next(){returnthis.dequeue().then(value=>(value===AsyncQueue.EOS)?{value:undefined,done:true}:{value:value,done:false});}}// A sentinel value returned by dequeue() to mark "end of stream" when closedAsyncQueue.EOS=Symbol("end-of-stream");
// Push events of the specified type on the specified document element// onto an AsyncQueue object, and return the queue for use as an event streamfunctioneventStream(elt,type){constq=newAsyncQueue();// Create a queueelt.addEventListener(type,e=>q.enqueue(e));// Enqueue eventsreturnq;}asyncfunctionhandleKeys(){// Get a stream of keypress events and loop once for each oneforawait(consteventofeventStream(document,"keypress")){console.log(event.key);}}
Most real-world JavaScript programming is asynchronous.
Traditionally, asynchrony has been handled with events and callback functions. This can get complicated, however, because you can end up with multiple levels of callbacks nested inside other callbacks, and because it is difficult to do robust error handling.
Promises provide a new way of structuring callback functions. If
used correctly (and unfortunately, Promises are easy to use
incorrectly), they can convert asynchronous code that would have
been nested into linear chains of then() calls where one
asynchronous step of a computation follows another. Also, Promises
allow you to centralize your error-handling code into a single
catch() call at the end of a chain of then() calls.
The async and await keywords allow us to write asynchronous code
that is Promise-based under the hood but that looks like
synchronous code. This makes the code easier to understand and
reason about. If a function is declared async, it will implicitly
return a Promise. Inside an async function, you can await
a Promise (or a function that returns a Promise) as if the Promise
value was synchronously computed.
Objects that are asynchronously iterable can be used with a
for/await loop. You can create asynchronously iterable objects by
implementing a [Symbol.asyncIterator]() method or by invoking an
async function * generator function. Asynchronous iterators
provide an alternative to “data” events on streams in Node and can
be used to represent a stream of user input events in client-side
JavaScript.
1 The XMLHttpRequest class has nothing in particular to do with XML. In modern client-side JavaScript, it has largely been replaced by the fetch() API, which is covered in §15.11.1. The code example shown here is the last XMLHttpRequest-based example remaining in this book.
2 You can typically use await at the top level in a browser’s developer console. And there is a pending proposal to allow top-level await in a future version of JavaScript.
3 I learned about this approach to asynchronous iteration from the blog of Dr. Axel Rauschmayer, https://2ality.com.
§14.1 Controlling the enumerability, deleteability, and configurability of object properties
§14.2 Controlling the extensibility of objects, and creating “sealed” and “frozen” objects
§14.3 Querying and setting the prototypes of objects
§14.4 Fine-tuning the behavior of your types with well-known Symbols
§14.5 Creating DSLs (domain-specific languages) with template tag functions
§14.6 Probing objects with reflect methods
§14.7 Controlling object behavior with Proxy
The writable attribute specifies whether or not the value of a property can change.
The enumerable attribute specifies whether the property is
enumerated by the for/in loop and the Object.keys() method.
The configurable attribute specifies whether a property can be deleted and also whether the property’s attributes can be changed.
It allows them to add methods to prototype objects and make them non-enumerable, like built-in methods.
It allows them to “lock down” their objects, defining properties that cannot be changed or deleted.
// Returns {value: 1, writable:true, enumerable:true, configurable:true}Object.getOwnPropertyDescriptor({x:1},"x");// Here is an object with a read-only accessor propertyconstrandom={getoctet(){returnMath.floor(Math.random()*256);},};// Returns { get: /*func*/, set:undefined, enumerable:true, configurable:true}Object.getOwnPropertyDescriptor(random,"octet");// Returns undefined for inherited properties and properties that don't exist.Object.getOwnPropertyDescriptor({},"x")// => undefined; no such propObject.getOwnPropertyDescriptor({},"toString")// => undefined; inherited
leto={};// Start with no properties at all// Add a non-enumerable data property x with value 1.Object.defineProperty(o,"x",{value:1,writable:true,enumerable:false,configurable:true});// Check that the property is there but is non-enumerableo.x// => 1Object.keys(o)// => []// Now modify the property x so that it is read-onlyObject.defineProperty(o,"x",{writable:false});// Try to change the value of the propertyo.x=2;// Fails silently or throws TypeError in strict modeo.x// => 1// The property is still configurable, so we can change its value like this:Object.defineProperty(o,"x",{value:2});o.x// => 2// Now change x from a data property to an accessor propertyObject.defineProperty(o,"x",{get:function(){return0;}});o.x// => 0
letp=Object.defineProperties({},{x:{value:1,writable:true,enumerable:true,configurable:true},y:{value:1,writable:true,enumerable:true,configurable:true},r:{get(){returnMath.sqrt(this.x*this.x+this.y*this.y);},enumerable:true,configurable:true}});p.r// => Math.SQRT2
If an object is not extensible, you can edit its existing own properties, but you cannot add new properties to it.
If a property is not configurable, you cannot change its configurable or enumerable attributes.
If an accessor property is not configurable, you cannot change its getter or setter method, and you cannot change it to a data property.
If a data property is not configurable, you cannot change it to an accessor property.
If a data property is not configurable, you cannot change its
writable attribute from false to true, but you can change it
from true to false.
If a data property is not configurable and not writable, you cannot change its value. You can change the value of a property that is configurable but nonwritable, however (because that would be the same as making it writable, then changing the value, then converting it back to nonwritable).
/** Define a new Object.assignDescriptors() function that works like* Object.assign() except that it copies property descriptors from* source objects into the target object instead of just copying* property values. This function copies all own properties, both* enumerable and non-enumerable. And because it copies descriptors,* it copies getter functions from source objects and overwrites setter* functions in the target object rather than invoking those getters and* setters.** Object.assignDescriptors() propagates any TypeErrors thrown by* Object.defineProperty(). This can occur if the target object is sealed* or frozen or if any of the source properties try to change an existing* non-configurable property on the target object.** Note that the assignDescriptors property is added to Object with* Object.defineProperty() so that the new function can be created as* a non-enumerable property like Object.assign().*/Object.defineProperty(Object,"assignDescriptors",{// Match the attributes of Object.assign()writable:true,enumerable:false,configurable:true,// The function that is the value of the assignDescriptors property.value:function(target,...sources){for(letsourceofsources){for(letnameofObject.getOwnPropertyNames(source)){letdesc=Object.getOwnPropertyDescriptor(source,name);Object.defineProperty(target,name,desc);}for(letsymbolofObject.getOwnPropertySymbols(source)){letdesc=Object.getOwnPropertyDescriptor(source,symbol);Object.defineProperty(target,symbol,desc);}}returntarget;}});leto={c:1,getcount(){returnthis.c++;}};// Define object with getterletp=Object.assign({},o);// Copy the property valuesletq=Object.assignDescriptors({},o);// Copy the property descriptorsp.count// => 1: This is now just a data property sop.count// => 1: ...the counter does not increment.q.count// => 2: Incremented once when we copied it the first time,q.count// => 3: ...but we copied the getter method so it increments.
Object.seal() works like Object.preventExtensions(), but in
addition to making the object non-extensible, it also makes all of
the own properties of that object nonconfigurable. This means that
new properties cannot be added to the object, and existing
properties cannot be deleted or configured. Existing properties that
are writable can still be set, however. There is no way to unseal a
sealed object. You can use Object.isSealed() to determine whether
an object is sealed.
Object.freeze() locks objects down even more tightly. In addition
to making the object non-extensible and its properties
nonconfigurable, it also makes all of the object’s own data
properties read-only. (If the object has accessor properties with
setter methods, these are not affected and can still be invoked by
assignment to the property.) Use Object.isFrozen() to determine
if an object is frozen.
// Create a sealed object with a frozen prototype and a non-enumerable propertyleto=Object.seal(Object.create(Object.freeze({x:1}),{y:{value:2,writable:true}}));
Object.getPrototypeOf({})// => Object.prototypeObject.getPrototypeOf([])// => Array.prototypeObject.getPrototypeOf(()=>{})// => Function.prototype
letp={x:1};// Define a prototype object.leto=Object.create(p);// Create an object with that prototype.p.isPrototypeOf(o)// => true: o inherits from pObject.prototype.isPrototypeOf(p)// => true: p inherits from Object.prototypeObject.prototype.isPrototypeOf(o)// => true: o does too
leto={x:1};letp={y:2};Object.setPrototypeOf(o,p);// Set the prototype of o to po.y// => 2: o now inherits the property yleta=[1,2,3];Object.setPrototypeOf(a,p);// Set the prototype of array a to pa.join// => undefined: a no longer has a join() method
letp={z:3};leto={x:1,y:2,__proto__:p};o.z// => 3: o inherits from p
// Define an object as a "type" we can use with instanceofletuint8={[Symbol.hasInstance](x){returnNumber.isInteger(x)&&x>=0&&x<=255;}};128instanceofuint8// => true256instanceofuint8// => false: too bigMath.PIinstanceofuint8// => false: not an integer
{}.toString()// => "[object Object]"
Object.prototype.toString.call([])// => "[object Array]"Object.prototype.toString.call(/./)// => "[object RegExp]"Object.prototype.toString.call(()=>{})// => "[object Function]"Object.prototype.toString.call("")// => "[object String]"Object.prototype.toString.call(0)// => "[object Number]"Object.prototype.toString.call(false)// => "[object Boolean]"
functionclassof(o){returnObject.prototype.toString.call(o).slice(8,-1);}classof(null)// => "Null"classof(undefined)// => "Undefined"classof(1)// => "Number"classof(10n**100n)// => "BigInt"classof("")// => "String"classof(false)// => "Boolean"classof(Symbol())// => "Symbol"classof({})// => "Object"classof([])// => "Array"classof(/./)// => "RegExp"classof(()=>{})// => "Function"classof(newMap())// => "Map"classof(newSet())// => "Set"classof(newDate())// => "Date"
classRange{get[Symbol.toStringTag](){return"Range";}// the rest of this class is omitted here}letr=newRange(1,10);Object.prototype.toString.call(r)// => "[object Range]"classof(r)// => "Range"
// A trivial Array subclass that adds getters for the first and last elements.classEZArrayextendsArray{getfirst(){returnthis[0];}getlast(){returnthis[this.length-1];}}lete=newEZArray(1,2,3);letf=e.map(x=>x*x);e.last// => 3: the last element of EZArray ef.last// => 9: f is also an EZArray with a last property
In ES6 and later, the Array() constructor has a property with the
symbolic name Symbol.species. (Note that this Symbol is used as
the name of a property of the constructor function. Most of the
other well-known Symbols described here are used as the name of
methods of a prototype object.)
When we create a subclass with extends, the resulting subclass
constructor inherits properties from the superclass
constructor. (This is in addition to the normal kind of inheritance,
where instances of the subclass inherit methods of the
superclass.) This means that the constructor for every subclass of
Array also has an inherited property with name
Symbol.species. (Or a subclass can define its own property
with this name, if it wants.)
Methods like map() and slice() that create and return new arrays
are tweaked slightly in ES6 and later. Instead of just creating a
regular Array, they (in effect) invoke new
this.constructor[Symbol.species]() to create the new array.
Array[Symbol.species]=Array;
EZArray[Symbol.species]=Array;// Attempt to set a read-only property fails// Instead we can use defineProperty():Object.defineProperty(EZArray,Symbol.species,{value:Array});
classEZArrayextendsArray{staticget[Symbol.species](){returnArray;}getfirst(){returnthis[0];}getlast(){returnthis[this.length-1];}}lete=newEZArray(1,2,3);letf=e.map(x=>x-1);e.last// => 3f.last// => undefined: f is a regular array with no last getter
If you create an Array-like (see §7.9) object and want it
to behave like a real array when passed to concat(), you can
simply add the symbolic property to your object:
letarraylike={length:1,0:1,[Symbol.isConcatSpreadable]:true};[].concat(arraylike)// => [1]: (would be [[1]] if not spread)
Array subclasses are spreadable by default, so if you are defining an
array subclass that you do not want to act like an array when used
with concat(), then you can1 add a getter like this to your subclass:
classNonSpreadableArrayextendsArray{get[Symbol.isConcatSpreadable](){returnfalse;}}leta=newNonSpreadableArray(1,2,3);[].concat(a).length// => 1; (would be 3 elements long if a was spread)
string.method(pattern,arg)
pattern[symbol](string,arg)
classGlob{constructor(glob){this.glob=glob;// We implement glob matching using RegExp internally.// ? matches any one character except /, and * matches zero or more// of those characters. We use capturing groups around each.letregexpText=glob.replace("?","([^/])").replace("*","([^/]*)");// We use the u flag to get Unicode-aware matching.// Globs are intended to match entire strings, so we use the ^ and $// anchors and do not implement search() or matchAll() since they// are not useful with patterns like this.this.regexp=newRegExp(`^${regexpText}$`,"u");}toString(){returnthis.glob;}[Symbol.search](s){returns.search(this.regexp);}[Symbol.match](s){returns.match(this.regexp);}[Symbol.replace](s,replacement){returns.replace(this.regexp,replacement);}}letpattern=newGlob("docs/*.txt");"docs/js.txt".search(pattern)// => 0: matches at character 0"docs/js.htm".search(pattern)// => -1: does not matchletmatch="docs/js.txt".match(pattern);match[0]// => "docs/js.txt"match[1]// => "js"match.index// => 0"docs/js.txt".replace(pattern,"web/$1.htm")// => "web/js.htm"
If the argument is "string", it means that JavaScript is doing the
conversion in a context where it would expect or prefer (but not
require) a string. This happens when you interpolate the object into
a template literal, for example.
If the argument is "number", it means that JavaScript is doing the
conversion in a context where it would expect or prefer (but not
require) a numeric value. This happens when you use the object with
a < or > operator or with arithmetic operators like - and
*.
If the argument is "default", it means that JavaScript is
converting your object in a context where either a numeric or string
value could work. This happens with the +, ==, and !=
operators.
letnewArrayMethods=Object.keys(Array.prototype[Symbol.unscopables]);
functionhtml(strings,...values){// Convert each value to a string and escape special HTML charactersletescaped=values.map(v=>String(v).replace("&","&").replace("<","<").replace(">",">").replace('"',""").replace("'","'"));// Return the concatenated strings and escaped valuesletresult=strings[0];for(leti=0;i<escaped.length;i++){result+=escaped[i]+strings[i+1];}returnresult;}letoperator="<";html`<b>x${operator}y</b>`// => "<b>x < y</b>"letkind="game",name="D&D";html`<div class="${kind}">${name}</div>`// =>'<div class="game">D&D</div>'
functionglob(strings,...values){// Assemble the strings and values into a single stringlets=strings[0];for(leti=0;i<values.length;i++){s+=values[i]+strings[i+1];}// Return a parsed representation of that stringreturnnewGlob(s);}letroot="/tmp";letfilePattern=glob`${root}/*.html`;// A RegExp alternative"/tmp/test.html".match(filePattern)[1]// => "test"
Reflect.apply(f, o, args)This function invokes the function f
as a method of o (or invokes it as a function with no this value
if o is null) and passes the values in the args array as
arguments. It is equivalent to f.apply(o, args).
Reflect.construct(c, args, newTarget)This function invokes the
constructor c as if the new keyword had been used and passes the
elements of the array args as arguments. If the optional newTarget
argument is specified, it is used as the value of new.target within
the constructor invocation. If not specified, then the new.target
value will be c.
Reflect.defineProperty(o, name, descriptor)This function defines
a property on the object o, using name (a string or symbol) as the
name of the property. The Descriptor object should define the value
(or getter and/or setter) and attributes of the
property. Reflect.defineProperty() is very similar to
Object.defineProperty() but returns true on success and false
on failures. (Object.defineProperty() returns o on success and
throws TypeError on failure.)
Reflect.deleteProperty(o, name)This function deletes the property
with the specified string or symbolic name from the object o,
returning true if successful (or if no such property existed) and
false if the property could not be deleted. Calling this function is
similar to writing delete o[name].
Reflect.get(o, name, receiver)This function returns the value of
the property of o with the specified name (a string or symbol). If the
property is an accessor method with a getter, and if the optional
receiver argument is specified, then the getter function is called as a
method of receiver instead of as a method of o. Calling this
function is similar to evaluating o[name].
Reflect.getOwnPropertyDescriptor(o, name)This function returns a
property descriptor object that describes the attributes of the
property named name of the object o, or returns undefined if no
such property exists. This function is nearly identical to
Object.getOwnPropertyDescriptor(), except that the Reflect API
version of the function requires that the first argument be an object
and throws TypeError if it is not.
Reflect.getPrototypeOf(o)This function returns the prototype
of object o or null if the object has no prototype. It throws a
TypeError if o is a primitive value instead of an object. This
function is almost identical to Object.getPrototypeOf() except that
Object.getPrototypeOf() only throws a TypeError for null and
undefined arguments and coerces other primitive values to their
wrapper objects.
Reflect.has(o, name)This function returns true if the object
o has a property with the specified name (which must be a string or a
symbol). Calling this function is similar to evaluating name in o.
Reflect.isExtensible(o)This function returns true if the object
o is extensible (§14.2) and false if it is not. It
throws a TypeError if o is not an object. Object.isExtensible() is
similar but simply returns false when passed an argument that is not
an object.
Reflect.ownKeys(o)This function returns an array of the names of
the properties of the object o or throws a TypeError if o is not
an object. The names in the returned array will be strings and/or
symbols. Calling this function is similar to calling
Object.getOwnPropertyNames() and Object.getOwnPropertySymbols()
and combining their results.
Reflect.preventExtensions(o)This function sets the extensible
attribute (§14.2) of the object o to false and
returns true to indicate success. It throws a TypeError if o is
not an object. Object.preventExtensions() has the same effect but
returns o instead of true and does not throw TypeError for
nonobject arguments.
Reflect.set(o, name, value, receiver)This function sets the
property with the specified name of the object o to the specified
value. It returns
true on success and false on failure (which can happen if the
property is read-only). It throws TypeError if o is not an
object. If the specified property is an accessor property with a
setter function, and if the optional receiver argument is passed, then
the setter will be invoked as a method of receiver instead of being
invoked as a method of o. Calling this function is usually the same
as evaluating o[name] = value.
Reflect.setPrototypeOf(o, p)This function sets the prototype of
the object o to p, returning true on success and false on
failure (which can occur if o is not extensible or if the operation
would cause a circular prototype chain). It throws a TypeError if o
is not an object or if p is neither an object nor
null. Object.setPrototypeOf() is similar, but returns o on
success and throws TypeError on failure. Remember that calling either
of these functions is likely to make your code slower by disrupting
JavaScript interpreter optimizations.
letproxy=newProxy(target,handlers);
lett={x:1,y:2};letp=newProxy(t,{});p.x// => 1deletep.y// => true: delete property y of the proxyt.y// => undefined: this deletes it in the target, toop.z=3;// Defining a new property on the proxyt.z// => 3: defines the property on the target
functionaccessTheDatabase(){/* implementation omitted */return42;}let{proxy,revoke}=Proxy.revocable(accessTheDatabase,{});proxy()// => 42: The proxy gives access to the underlying target functionrevoke();// But that access can be turned off whenever we wantproxy();// !TypeError: we can no longer call this function
// We use a Proxy to create an object that appears to have every// possible property, with the value of each property equal to its nameletidentity=newProxy({},{// Every property has its own name as its valueget(o,name,target){returnname;},// Every property name is definedhas(o,name){returntrue;},// There are too many properties to enumerate, so we just throwownKeys(o){thrownewRangeError("Infinite number of properties");},// All properties exist and are not writable, configurable or enumerable.getOwnPropertyDescriptor(o,name){return{value:name,enumerable:false,writable:false,configurable:false};},// All properties are read-only so they can't be setset(o,name,value,target){returnfalse;},// All properties are non-configurable, so they can't be deleteddeleteProperty(o,name){returnfalse;},// All properties exist and are non-configurable so we can't define moredefineProperty(o,name,desc){returnfalse;},// In effect, this means that the object is not extensibleisExtensible(o){returnfalse;},// All properties are already defined on this object, so it couldn't// inherit anything even if it did have a prototype object.getPrototypeOf(o){returnnull;},// The object is not extensible, so we can't change the prototypesetPrototypeOf(o,proto){returnfalse;},});identity.x// => "x"identity.toString// => "toString"identity[0]// => "0"identity.x=1;// Setting properties has no effectidentity.x// => "x"deleteidentity.x// => false: can't delete properties eitheridentity.x// => "x"Object.keys(identity);// !RangeError: can't list all the keysfor(letpofidentity);// !RangeError
functionreadOnlyProxy(o){functionreadonly(){thrownewTypeError("Readonly");}returnnewProxy(o,{set:readonly,defineProperty:readonly,deleteProperty:readonly,setPrototypeOf:readonly,});}leto={x:1,y:2};// Normal writable objectletp=readOnlyProxy(o);// Readonly version of itp.x// => 1: reading properties worksp.x=2;// !TypeError: can't change propertiesdeletep.y;// !TypeError: can't delete propertiesp.z=3;// !TypeError: can't add propertiesp.__proto__={};// !TypeError: can't change the prototype
/** Return a Proxy object that wraps o, delegating all operations to* that object after logging each operation. objname is a string that* will appear in the log messages to identify the object. If o has own* properties whose values are objects or functions, then if you query* the value of those properties, you'll get a loggingProxy back, so that* logging behavior of this proxy is "contagious".*/functionloggingProxy(o,objname){// Define handlers for our logging Proxy object.// Each handler logs a message and then delegates to the target object.consthandlers={// This handler is a special case because for own properties// whose value is an object or function, it returns a proxy rather// than returning the value itself.get(target,property,receiver){// Log the get operationconsole.log(`Handler get(${objname},${property.toString()})`);// Use the Reflect API to get the property valueletvalue=Reflect.get(target,property,receiver);// If the property is an own property of the target and// the value is an object or function then return a Proxy for it.if(Reflect.ownKeys(target).includes(property)&&(typeofvalue==="object"||typeofvalue==="function")){returnloggingProxy(value,`${objname}.${property.toString()}`);}// Otherwise return the value unmodified.returnvalue;},// There is nothing special about the following three methods:// they log the operation and delegate to the target object.// They are a special case simply so we can avoid logging the// receiver object which can cause infinite recursion.set(target,prop,value,receiver){console.log(`Handler set(${objname},${prop.toString()},${value})`);returnReflect.set(target,prop,value,receiver);},apply(target,receiver,args){console.log(`Handler${objname}(${args})`);returnReflect.apply(target,receiver,args);},construct(target,args,receiver){console.log(`Handler${objname}(${args})`);returnReflect.construct(target,args,receiver);}};// We can automatically generate the rest of the handlers.// Metaprogramming FTW!Reflect.ownKeys(Reflect).forEach(handlerName=>{if(!(handlerNameinhandlers)){handlers[handlerName]=function(target,...args){// Log the operationconsole.log(`Handler${handlerName}(${objname},${args})`);// Delegate the operationreturnReflect[handlerName](target,...args);};}});// Return a proxy for the object using these logging handlersreturnnewProxy(o,handlers);}
// Define an array of data and an object with a function propertyletdata=[10,20];letmethods={square:x=>x*x};// Create logging proxies for the array and the objectletproxyData=loggingProxy(data,"data");letproxyMethods=loggingProxy(methods,"methods");// Suppose we want to understand how the Array.map() method worksdata.map(methods.square)// => [100, 400]// First, let's try it with a logging Proxy arrayproxyData.map(methods.square)// => [100, 400]// It produces this output:// Handler get(data,map)// Handler get(data,length)// Handler get(data,constructor)// Handler has(data,0)// Handler get(data,0)// Handler has(data,1)// Handler get(data,1)// Now lets try with a proxy methods objectdata.map(proxyMethods.square)// => [100, 400]// Log output:// Handler get(methods,square)// Handler methods.square(10,0,10,20)// Handler methods.square(20,1,10,20)// Finally, let's use a logging proxy to learn about the iteration protocolfor(letxofproxyData)console.log("Datum",x);// Log output:// Handler get(data,Symbol(Symbol.iterator))// Handler get(data,length)// Handler get(data,0)// Datum 10// Handler get(data,length)// Handler get(data,1)// Datum 20// Handler get(data,length)
lettarget=Object.preventExtensions({});letproxy=newProxy(target,{isExtensible(){returntrue;}});Reflect.isExtensible(proxy);// !TypeError: invariant violation
lettarget=Object.freeze({x:1});letproxy=newProxy(target,{get(){return99;}});proxy.x;// !TypeError: value returned by get() doesn't match target
JavaScript objects have an extensible attribute and object properties have writable, enumerable, and configurable attributes, as well as a value and a getter and/or setter attribute. You can use these attributes to “lock down” your objects in various ways, including creating “sealed” and “frozen” objects.
JavaScript defines functions that allow you to traverse the prototype chain of an object and even to change the prototype of an object (though doing this can make your code slower).
The properties of the Symbol object have values that are
“well-known Symbols,” which you can use as property or method names
for the objects and classes that you define. Doing so allows you to
control how your object interacts with JavaScript language features
and with the core library. For example, well-known Symbols allow you
to make your classes iterable and control the string that is
displayed when an instance is passed to
Object.prototype.toString(). Prior to ES6, this kind of
customization was available only to the native classes that were
built in to an implementation.
Tagged template literals are a function invocation syntax, and defining a new tag function is kind of like adding a new literal syntax to the language. Defining a tag function that parses its template string argument allows you to embed DSLs within JavaScript code. Tag functions also provide access to a raw, unescaped form of string literals where backslashes have no special meaning.
The Proxy class and the related Reflect API allow low-level control over the fundamental behaviors of JavaScript objects. Proxy objects can be used as optionally revocable wrappers to improve code encapsulation, and they can also be used to implement nonstandard object behaviors (like some of the special case APIs defined by early web browsers).
1 A bug in the V8 JavaScript engine means that this code does not work correctly in Node 13.
Determine the on-screen position of document elements (§15.5)
Create reusable user interface components (§15.6)
Play and generate sounds (§15.9)
Manage browser navigation and history (§15.10)
Exchange data over the network (§15.11)
Store data on the user’s computer (§15.12)
Perform concurrent computation with threads (§15.13)
<!DOCTYPE html><!-- This is an HTML5 file --><html><!-- The root element --><head><!-- Title, scripts & styles can go here --><title>Digital Clock</title><style>/* A CSS stylesheet for the clock */#clock{/* Styles apply to element with id="clock" */font:bold24pxsans-serif;/* Use a big bold font */background:#ddf;/* on a light bluish-gray background. */padding:15px;/* Surround it with some space */border:solidblack2px;/* and a solid black border */border-radius:10px;/* with rounded corners. */}</style></head><body><!-- The body holds the content of the document. --><h1>Digital Clock</h1><!-- Display a title. --><spanid="clock"></span><!-- We will insert the time into this element. --><script>// Define a function to display the current timefunctiondisplayTime(){letclock=document.querySelector("#clock");// Get element with id="clock"letnow=newDate();// Get current timeclock.textContent=now.toLocaleTimeString();// Display time in the clock}displayTime()// Display the time right awaysetInterval(displayTime,1000);// And then update it every second.</script></body></html>
<scriptsrc="scripts/digital_clock.js"></script>
It simplifies your HTML files by allowing you to remove large blocks of JavaScript code from them—that is, it helps keep content and behavior separate.
When multiple web pages share the same JavaScript code, using the
src attribute allows you to maintain only a single copy of that code,
rather than having to edit each HTML file when the code changes.
If a file of JavaScript code is shared by more than one page, it only needs to be downloaded once, by the first page that uses it—subsequent pages can retrieve it from the browser cache.
Because the src attribute takes an arbitrary URL as its value, a
JavaScript program or web page from one web server can employ code
exported by other web servers. Much internet advertising relies on this
fact.
To specify that the script is a module
To embed data into a web page without displaying it (see §15.3.4)
<scriptdefersrc="deferred.js"></script><scriptasyncsrc="async.js"></script>
// Asynchronously load and execute a script from a specified URL// Returns a Promise that resolves when the script has loaded.functionimportScript(url){returnnewPromise((resolve,reject)=>{lets=document.createElement("script");// Create a <script> elements.onload=()=>{resolve();};// Resolve promise when loadeds.onerror=(e)=>{reject(e);};// Reject on failures.src=url;// Set the script URLdocument.head.append(s);// Add <script> to document});}
<html><head><title>Sample Document</title></head><body><h1>An HTML Document</h1><p>This is a<i>simple</i>document.</body></html>
The web browser creates a Document object and begins parsing the web
page, adding Element objects and Text nodes to the document as it
parses HTML elements and their textual content. The
document.readyState property has the value “loading” at this
stage.
When the HTML parser encounters a <script> tag that does not have
any of the async, defer, or type="module" attributes, it adds
that script tag to the document and then executes the script. The
script is executed synchronously, and the HTML parser pauses while
the script downloads (if necessary) and runs. A script like this can
use document.write() to insert text into the input stream, and
that text will become part of the document when the parser
resumes. A script like this often simply defines functions and
registers event handlers for later use, but it can traverse and
manipulate the document tree as it exists at that time. That is,
non-module scripts that do not have an async or defer attribute
can see their own <script> tag and document content that comes
before it.
When the parser encounters a <script> element that has the async
attribute set, it begins downloading the script text (and if the
script is a module, it also recursively downloads all of the
script’s dependencies) and continues parsing the document. The
script will be executed as soon as possible after it has downloaded,
but the parser does not stop and wait for it to
download. Asynchronous scripts must not use the document.write()
method. They can see their own <script> tag and all document
content that comes before it, and may or may not have access to
additional document content.
When the document is completely parsed, the document.readyState
property changes to “interactive.”
Any scripts that had the defer attribute set (along with any module
scripts that do not have an async attribute) are executed in the order
in which they appeared in the document. Async scripts may also be
executed at this time. Deferred scripts have access to the complete
document and they must not use the
document.write() method.
The browser fires a “DOMContentLoaded” event on the Document object.
This marks the transition from synchronous script-execution phase to
the asynchronous, event-driven phase of program execution. Note,
however, that there may still be async scripts that have not yet
executed at this point.
The document is completely parsed at this point, but the browser may
still be waiting for additional content, such as images, to
load. When all such content finishes loading, and when all async
scripts have loaded and executed, the document.readyState property
changes to “complete” and the web browser fires a “load” event on the
Window object.
From this point on, event handlers are invoked asynchronously in response to user input events, network events, timer expirations, and so on.
The content of the document itself, which JavaScript code can access with the DOM API (§15.3).
User input, in the form of events, such as mouse clicks (or
touch-screen taps) on HTML <button> elements, or text entered into
HTML <textarea> elements, for example. §15.2 demonstrates how
JavaScript programs can respond to user events like these.
The URL of the document being displayed is available to client-side
JavaScript as document.URL. If you pass this string to the URL()
constructor (§11.9), you can easily access the path, query, and
fragment sections of the URL.
The content of the HTTP “Cookie” request header is available to
client-side code as document.cookie. Cookies are usually used by
server-side code for maintaining user sessions, but client-side code
can also read (and write) them if necessary. See §15.12.2 for
further details.
The global navigator property provides access to information about
the web browser, the OS it’s running on top of, and the
capabilities of each. For example, navigator.userAgent is a
string that identifies the web browser, navigator.language is the
user’s preferred language, and navigator.hardwareConcurrency
returns the number of logical CPUs available to the web
browser. Similarly, the global screen property provides access to
the user’s display size via the screen.width and screen.height
properties. In a sense, these navigator and screen objects are
to web browsers what environment variables are to Node programs.
Defining powerful client-side APIs to enable useful web applications
Preventing malicious code from reading or altering your data, compromising your privacy, scamming you, or wasting your time
<script>letname=newURL(document.URL).searchParams.get("name");document.querySelector('h1').innerHTML="Hello "+name;</script>
http://www.example.com/greet.html?name=David
name=%3Cimg%20src=%22x.png%22%20onload=%22alert(%27hacked%27)%22/%3E
Hello <img src="x.png" onload="alert('hacked')"/>
name=name.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")
This string specifies what kind of event occurred. The type “mousemove,” for example, means that the user moved the mouse. The type “keydown” means that the user pressed a key on the keyboard down. And the type “load” means that a document (or some other resource) has finished loading from the network. Because the type of an event is just a string, it’s sometimes called an event name, and indeed, we use this name to identify the kind of event we’re talking about.
This is the object on which the event occurred or with
which the event is associated. When we speak of an event, we must
specify both the type and the target. A load event on a Window, for
example, or a click event on a <button> Element. Window, Document,
and Element objects are the most common event targets in client-side
JavaScript applications, but some events are triggered on other kinds
of objects. For example, a Worker object (a kind of thread, covered
§15.13) is a target for “message” events that occur when the
worker thread sends a message to the main thread.
This function handles or responds to an event.2 Applications register their event handler functions with the web browser, specifying an event type and an event target. When an event of the specified type occurs on the specified target, the browser invokes the handler function. When event handlers are invoked for an object, we say that the browser has “fired,” “triggered,” or “dispatched” the event. There are a number of ways to register event handlers, and the details of handler registration and invocation are explained in §15.2.2 and §15.2.3.
This object is associated with a particular
event and contains details about that event. Event objects are passed
as an argument to the event handler function. All event objects have a
type property that specifies the event type and a target property
that specifies the event target. Each event type defines a set of
properties for its associated event object. The object associated with
a mouse event includes the coordinates of the mouse pointer, for
example, and the object associated with a keyboard event contains
details about the key that was pressed and the modifier keys that were
held down. Many event types define only a few standard properties—such
as type and target—and do not carry much other useful
information. For those events, it is the simple occurrence of the
event, not the event details, that matter.
This is the process by which the browser decides which
objects to trigger event handlers on. For events that are specific to
a single object—such as the “load” event on the Window object or a
“message” event on a Worker object—no propagation is required. But
when certain kinds of events occur on elements within the HTML
document, however, they propagate or “bubble” up the document tree.
If the user moves the mouse over a hyperlink, the mousemove event is
first fired on the <a> element that defines that link. Then it is
fired on the containing elements: perhaps a <p> element, a <section>
element, and the Document object itself. It is sometimes more
convenient to register a single event handler on a Document or other
container element than to register handlers on each individual element
you’re interested in. An event handler can stop the propagation of an
event so that it will not continue to bubble and will not trigger
handlers on containing elements. Handlers do this by invoking a method
of the event object. In another form of event
propagation, known as event capturing, handlers specially registered
on container elements have the opportunity to intercept (or “capture”)
events before they are delivered to their actual target. Event
bubbling and capturing are covered in detail in §15.2.4.
These events are directly tied to a specific input device, such as the mouse or keyboard. They include event types such as “mousedown,” “mousemove,” “mouseup,” “touchstart,” “touchmove,” “touchend,” “keydown,” and “keyup.”
These input events are not directly tied to a specific input device. The “click” event, for example, indicates that a link or button (or other document element) has been activated. This is often done via a mouse click, but it could also be done by keyboard or (on touch-sensitive devices) with a tap. The “input” event is a device-independent alternative to the “keydown” event and supports keyboard input as well as alternatives such as cut-and-paste and input methods used for ideographic scripts. The “pointerdown,” “pointermove,” and “pointerup” event types are device-independent alternatives to mouse and touch events. They work for mouse-type pointers, for touch screens, and for pen- or stylus-style input as well.
UI events are higher-level events, often on HTML form elements that define a user interface for a web application. They include the “focus” event (when a text input field gains keyboard focus), the “change” event (when the user changes the value displayed by a form element), and the “submit” event (when the user clicks a Submit button in a form).
Some events are not triggered directly by user activity, but by network or browser activity, and indicate some kind of life-cycle or state-related change. The “load” and “DOMContentLoaded” events—fired on the Window and Document objects, respectively, at the end of document loading—are probably the most commonly used of these events (see “Client-side JavaScript timeline”). Browsers fire “online” and “offline” events on the Window object when network connectivity changes. The browser’s history management mechanism (§15.10.4) fires the “popstate” event in response to the browser’s Back button.
A number of web APIs defined by HTML and
related specifications include their own event types. The HTML
<video> and <audio> elements define a long list of
associated event types such as “waiting,” “playing,” “seeking,”
“volumechange,” and so on, and you can use them to customize media
playback. Generally
speaking, web platform APIs that are asynchronous
and were developed before Promises were added to JavaScript are
event-based and define API-specific events. The IndexedDB API, for
example (§15.12.3), fires “success” and “error” events when database
requests succeed or fail. And although the new fetch() API (§15.11.1)
for making HTTP requests is Promise-based, the XMLHttpRequest API
that it replaces defines a number of API-specific event types.
// Set the onload property of the Window object to a function.// The function is the event handler: it is invoked when the document loads.window.onload=function(){// Look up a <form> elementletform=document.querySelector("form#shipping");// Register an event handler function on the form that will be invoked// before the form is submitted. Assume isFormValid() is defined elsewhere.form.onsubmit=function(event){// When the user submits the formif(!isFormValid(this)){// check whether form inputs are validevent.preventDefault();// and if not, prevent form submission.}};};
<buttononclick="console.log('Thank you');">Please Click</button>
function(event){with(document){with(this.form||{}){with(this){/* your code here */}}}}
<buttonid="mybutton">Click me</button><script>letb=document.querySelector("#mybutton");b.onclick=function(){console.log("Thanks for clicking me!");};b.addEventListener("click",()=>{console.log("Thanks again!");});</script>
document.removeEventListener("mousemove",handleMouseMove);document.removeEventListener("mouseup",handleMouseUp);
document.addEventListener("click",handleClick,{capture:true,once:true,passive:true});
typeThe type of the event that occurred.
targetThe object on which the event occurred.
currentTargetFor events that propagate, this property is the object on which the current event handler was registered.
timeStampA timestamp (in milliseconds) that represents when the event occurred but that does not represent an absolute time. You can determine the elapsed time between two events by subtracting the timestamp of the first event from the timestamp of the second.
isTrustedThis property will be true if the event was dispatched
by the web browser itself and false if the event was dispatched by
JavaScript code.
target.onclick=function(){/* handler code */};
// Dispatch a custom event so the UI knows we are busydocument.dispatchEvent(newCustomEvent("busy",{detail:true}));// Perform a network operationfetch(url).then(handleNetworkResponse).catch(handleNetworkError).finally(()=>{// After the network request has succeeded or failed, dispatch// another event to let the UI know that we are no longer busy.document.dispatchEvent(newCustomEvent("busy",{detail:false}));});// Elsewhere, in your program you can register a handler for "busy" events// and use it to show or hide the spinner to let the user know.document.addEventListener("busy",(e)=>{if(e.detail){showSpinner();}else{hideSpinner();}});
How to query or select individual elements from a document.
How to traverse a document, and how to find the ancestors, siblings, and descendants of any document element.
How to query and set the attributes of document elements.
How to query, set, and modify the content of a document.
How to modify the structure of a document by creating, inserting, and deleting nodes.
div//Any<div>element#nav//Theelementwithid="nav".warning//Anyelementwith"warning"initsclassattribute
p[lang="fr"]//AparagraphwritteninFrench:<plang="fr">*[name="x"]//Anyelementwithaname="x"attribute
span.fatal.error//Any<span>with"fatal"and"error"initsclassspan[lang="fr"].warning//Any<span>inFrenchwithclass"warning"
#logspan//Any<span>descendantoftheelementwithid="log"#log>span//Any<span>childoftheelementwithid="log"body>h1:first-child//Thefirst<h1>childofthe<body>img+p.caption//A<p>withclass"caption"immediatelyafteran<img>h2~p//Any<p>thatfollowsan<h2>andisasiblingofit
button,input[type="button"]//All<button>and<inputtype="button">elements
// Find the document element for the HTML tag with attribute id="spinner"letspinner=document.querySelector("#spinner");
// Find all Element objects for <h1>, <h2>, and <h3> tagslettitles=document.querySelectorAll("h1, h2, h3");
// Find the closest enclosing <a> tag that has an href attribute.lethyperlink=event.target.closest("a[href]");
// Return true if the element e is inside of an HTML list elementfunctioninsideList(e){returne.closest("ul,ol,dl")!==null;}
// Return true if e is an HTML heading elementfunctionisHeading(e){returne.matches("h1,h2,h3,h4,h5,h6");}
// Look up an element by id. The argument is just the id, without// the CSS selector prefix #. Similar to document.querySelector("#sect1")letsect1=document.getElementById("sect1");// Look up all elements (such as form checkboxes) that have a name="color"// attribute. Similar to document.querySelectorAll('*[name="color"]');letcolors=document.getElementsByName("color");// Look up all <h1> elements in the document.// Similar to document.querySelectorAll("h1")letheadings=document.getElementsByTagName("h1");// getElementsByTagName() is also defined on elements.// Get all <h2> elements within the sect1 element.letsubheads=sect1.getElementsByTagName("h2");// Look up all elements that have class "tooltip."// Similar to document.querySelectorAll(".tooltip")lettooltips=document.getElementsByClassName("tooltip");// Look up all descendants of sect1 that have class "sidebar"// Similar to sect1.querySelectorAll(".sidebar")letsidebars=sect1.getElementsByClassName("sidebar");
document.forms.address;
parentNodeThis property of an element refers to the parent of the element, which will be another Element or a Document object.
childrenThis NodeList contains the Element children of an element, but excludes non-Element children like Text nodes (and Comment nodes).
childElementCountThe number of Element children. Returns the same
value as children.length.
firstElementChild, lastElementChildThese properties refer to
the first and last Element children of an Element. They are null if
the Element has no Element children.
nextElementSibling, previousElementSiblingThese properties
refer to the sibling Elements immediately before or immediately after
an Element, or null if there is no such sibling.
document.children[0].children[1]document.firstElementChild.firstElementChild.nextElementSibling
// Recursively traverse the Document or Element e, invoking the function// f on e and on each of its descendantsfunctiontraverse(e,f){f(e);// Invoke f() on efor(letchildofe.children){// Iterate over the childrentraverse(child,f);// And recurse on each one}}functiontraverse2(e,f){f(e);// Invoke f() on eletchild=e.firstElementChild;// Iterate the children linked-list stylewhile(child!==null){traverse2(child,f);// And recursechild=child.nextElementSibling;}}
parentNodeThe node that is the parent of this one, or null for
nodes like the Document object that have no parent.
childNodesA read-only NodeList that that contains all children (not just Element children) of the node.
firstChild, lastChildThe first and last child nodes of a node,
or null if the node has no children.
nextSibling, previousSiblingThe next and previous sibling nodes of a node. These properties connect nodes in a doubly linked list.
nodeTypeA number that specifies what kind of node this is. Document nodes have value 9. Element nodes have value 1. Text nodes have value 3. Comment nodes have value 8.
nodeValueThe textual content of a Text or Comment node.
nodeNameThe HTML tag name of an Element, converted to uppercase.
document.childNodes[0].childNodes[1]document.firstChild.firstChild.nextSibling
<html><head><title>Test</title></head><body>Hello World!</body></html>
// Return the plain-text content of element e, recursing into child elements.// This method works like the textContent propertyfunctiontextContent(e){lets="";// Accumulate the text herefor(letchild=e.firstChild;child!==null;child=child.nextSibling){lettype=child.nodeType;if(type===3){// If it is a Text nodes+=child.nodeValue;// add the text content to our string.}elseif(type===1){// And if it is an Element nodes+=textContent(child);// then recurse.}}returns;}
letimage=document.querySelector("#main_image");leturl=image.src;// The src attribute is the URL of the imageimage.id==="main_image"// => true; we looked up the image by id
letf=document.querySelector("form");// First <form> in the documentf.action="https://www.example.com/submit";// Set the URL to submit it to.f.method="POST";// Set the HTTP request type.
// When we want to let the user know that we are busy, we display// a spinner. To do this we have to remove the "hidden" class and add the// "animated" class (assuming the stylesheets are configured correctly).letspinner=document.querySelector("#spinner");spinner.classList.remove("hidden");spinner.classList.add("animated");
<h2id="title"data-section-number="16.1">Attributes</h2>
letnumber=document.querySelector("#title").dataset.sectionNumber;
The content is the HTML string “This is a <i>simple</i> document”.
The content is the plain-text string “This is a simple document”.
document.body.innerHTML="<h1>Oops</h1>";
letpara=document.querySelector("p");// First <p> in the documentlettext=para.textContent;// Get the text of the paragraphpara.textContent="Hello World!";// Alter the text of the paragraph
letparagraph=document.createElement("p");// Create an empty <p> elementletemphasis=document.createElement("em");// Create an empty <em> elementemphasis.append("World");// Add text to the <em> elementparagraph.append("Hello ",emphasis,"!");// Add text and <em> to <p>paragraph.prepend("¡");// Add more text at start of <p>paragraph.innerHTML// => "¡Hello <em>World</em>!"
// Find the heading element with class="greetings"letgreetings=document.querySelector("h2.greetings");// Now insert the new paragraph and a horizontal rule after that headinggreetings.after(paragraph,document.createElement("hr"));
// We inserted the paragraph after this element, but now we// move it so it appears before the element insteadgreetings.before(paragraph);
// Make a copy of the paragraph and insert it after the greetings elementgreetings.after(paragraph.cloneNode(true));
// Remove the greetings element from the document and replace it with// the paragraph element (moving the paragraph from its current location// if it is already inserted into the document).greetings.replaceWith(paragraph);// And now remove the paragraph.paragraph.remove();
/*** TOC.js: create a table of contents for a document.** This script runs when the DOMContentLoaded event is fired and* automatically generates a table of contents for the document.* It does not define any global symbols so it should not conflict* with other scripts.** When this script runs, it first looks for a document element with* an id of "TOC". If there is no such element it creates one at the* start of the document. Next, the function finds all <h2> through* <h6> tags, treats them as section titles, and creates a table of* contents within the TOC element. The function adds section numbers* to each section heading and wraps the headings in named anchors so* that the TOC can link to them. The generated anchors have names* that begin with "TOC", so you should avoid this prefix in your own* HTML.** The entries in the generated TOC can be styled with CSS. All* entries have a class "TOCEntry". Entries also have a class that* corresponds to the level of the section heading. <h1> tags generate* entries of class "TOCLevel1", <h2> tags generate entries of class* "TOCLevel2", and so on. Section numbers inserted into headings have* class "TOCSectNum".** You might use this script with a stylesheet like this:** #TOC { border: solid black 1px; margin: 10px; padding: 10px; }* .TOCEntry { margin: 5px 0px; }* .TOCEntry a { text-decoration: none; }* .TOCLevel1 { font-size: 16pt; font-weight: bold; }* .TOCLevel2 { font-size: 14pt; margin-left: .25in; }* .TOCLevel3 { font-size: 12pt; margin-left: .5in; }* .TOCSectNum:after { content: ": "; }** To hide the section numbers, use this:** .TOCSectNum { display: none }**/document.addEventListener("DOMContentLoaded",()=>{// Find the TOC container element.// If there isn't one, create one at the start of the document.lettoc=document.querySelector("#TOC");if(!toc){toc=document.createElement("div");toc.id="TOC";document.body.prepend(toc);}// Find all section heading elements. We're assuming here that the// document title uses <h1> and that sections within the document are// marked with <h2> through <h6>.letheadings=document.querySelectorAll("h2,h3,h4,h5,h6");// Initialize an array that keeps track of section numbers.letsectionNumbers=[0,0,0,0,0];// Now loop through the section header elements we found.for(letheadingofheadings){// Skip the heading if it is inside the TOC container.if(heading.parentNode===toc){continue;}// Figure out what level heading it is.// Subtract 1 because <h2> is a level-1 heading.letlevel=parseInt(heading.tagName.charAt(1))-1;// Increment the section number for this heading level// and reset all lower heading level numbers to zero.sectionNumbers[level-1]++;for(leti=level;i<sectionNumbers.length;i++){sectionNumbers[i]=0;}// Now combine section numbers for all heading levels// to produce a section number like 2.3.1.letsectionNumber=sectionNumbers.slice(0,level).join(".");// Add the section number to the section header title.// We place the number in a <span> to make it styleable.letspan=document.createElement("span");span.className="TOCSectNum";span.textContent=sectionNumber;heading.prepend(span);// Wrap the heading in a named anchor so we can link to it.letanchor=document.createElement("a");letfragmentName=`TOC${sectionNumber}`;anchor.name=fragmentName;heading.before(anchor);// Insert anchor before headinganchor.append(heading);// and move heading inside anchor// Now create a link to this section.letlink=document.createElement("a");link.href=`#${fragmentName}`;// Link destination// Copy the heading text into the link. This is a safe use of// innerHTML because we are not inserting any untrusted strings.link.innerHTML=heading.innerHTML;// Place the link in a div that is styleable based on the level.letentry=document.createElement("div");entry.classList.add("TOCEntry",`TOCLevel${level}`);entry.append(link);// And add the div to the TOC container.toc.append(entry);}});
Setting the display style to “none” hides an element. You can
later show the element by setting display to some other value.
You can dynamically position elements by setting the position
style to “absolute,” “relative,” or “fixed” and then setting the
top and left styles to the desired coordinates. This is
important when using JavaScript to display dynamic content like
modal dialogues and tooltips.
You can shift, scale, and rotate elements with the transform
style.
You can animate changes to other CSS styles with the transition
style. These animations are handled automatically by the web browser
and do not require JavaScript, but you can use JavaScript to
initiate the animations.
.hidden{display:none;}
// Assume that this "tooltip" element has class="hidden" in the HTML file.// We can make it visible like this:document.querySelector("#tooltip").classList.remove("hidden");// And we can hide it again like this:document.querySelector("#tooltip").classList.add("hidden");
functiondisplayAt(tooltip,x,y){tooltip.style.display="block";tooltip.style.position="absolute";tooltip.style.left=`${x}px`;tooltip.style.top=`${y}px`;}
display:block;font-family:sans-serif;background-color:#ffffff;
e.style.display="block";e.style.fontFamily="sans-serif";e.style.backgroundColor="#ffffff";
e.style.marginLeft=300;// Incorrect: this is a number, not a stringe.style.marginLeft="300";// Incorrect: the units are missing
e.style.marginLeft="300px";
e.style.left=`${x0+left_border+left_padding}px`;
e.style.margin=`${top}px${right}px${bottom}px${left}px`;
// Copy the inline styles of element e to element f:f.setAttribute("style",e.getAttribute("style"));// Or do it like this:f.style.cssText=e.style.cssText;
lettitle=document.querySelector("#section1title");letstyles=window.getComputedStyle(title);letbeforeStyles=window.getComputedStyle(title,"::before");
Computed style properties are read-only.
Computed style properties are absolute: relative units like percentages and points are converted to absolute values. Any property that specifies a size (such as a margin size or a font size) will have a value measured in pixels. This value will be a string with a “px” suffix, so you’ll still need to parse it, but you won’t have to worry about parsing or converting other units. Properties whose values are colors will be returned in “rgb()” or “rgba()” format.
Shortcut properties are not computed—only the fundamental
properties that they are based on are. Don’t query the margin
property, for example, but use marginLeft, marginTop, and so
on. Similarly, don’t query border or even borderWidth. Instead,
use borderLeftWidth, borderTopWidth, and so on.
The cssText property of the computed style is undefined.
// This function switches between the "light" and "dark" themesfunctiontoggleTheme(){letlightTheme=document.querySelector("#light-theme");letdarkTheme=document.querySelector("#dark-theme");if(darkTheme.disabled){// Currently light, switch to darklightTheme.disabled=true;darkTheme.disabled=false;}else{// Currently dark, switch to lightlightTheme.disabled=false;darkTheme.disabled=true;}}
functionsetTheme(name){// Create a new <link rel="stylesheet"> element to load the named stylesheetletlink=document.createElement("link");link.id="theme";link.rel="stylesheet";link.href=`themes/${name}.css`;// Look for an existing link with id "theme"letcurrentTheme=document.querySelector("#theme");if(currentTheme){// If there is an existing theme, replace it with the new one.currentTheme.replaceWith(link);}else{// Otherwise, just insert the link to the theme stylesheet.document.head.append(link);}}
document.head.insertAdjacentHTML("beforeend","<style>body{transform:rotate(180deg)}</style>");
.transparent{opacity:0;}.fadeable{transition:opacity.5sease-in}
<divid="subscribe"class="fadeable notification">...</div>
document.querySelector("#subscribe").classList.add("transparent");
// Get the heights of the document and viewport.letdocumentHeight=document.documentElement.offsetHeight;letviewportHeight=window.innerHeight;// And scroll so the last "page" shows in the viewportwindow.scrollTo(0,documentHeight-viewportHeight);
// Scroll 50 pixels down every 500 ms. Note there is no way to turn this off!setInterval(()=>{scrollBy(0,50)},500);
window.scrollTo({left:0,top:documentHeight-viewportHeight,behavior:"smooth"});
offsetWidth clientWidth scrollWidth offsetHeight clientHeight scrollHeight offsetLeft clientLeft scrollLeft offsetTop clientTop scrollTop offsetParent
<scripttype="module"src="components/search-box.js">
<search-boxplaceholder="Search..."></search-box>
<search-box><imgsrc="images/search-icon.png"slot="left"/><imgsrc="images/cancel-icon.png"slot="right"/></search-box>
/** Make the <search-box> component invisible before it is defined.* And try to duplicate its eventual layout and size so that nearby* content does not move when it becomes defined.*/search-box:not(:defined){opacity:0;display:inline-block;width:300px;height:50px;}
lettableBody=document.querySelector("tbody");lettemplate=document.querySelector("#row");letclone=template.content.cloneNode(true);// deep clone// ...Use the DOM to insert content into the <td> elements of the clone...// Now add the cloned and initialized row into the tabletableBody.append(clone);
<p>The document has one marble:<inline-circle></inline-circle>. The HTML parser instantiates two more marbles:<inline-circlediameter="1.2em"color="blue"></inline-circle><inline-circlediameter=".6em"color="gold"></inline-circle>. How many marbles does the document contain now?</p>
customElements.define("inline-circle",classInlineCircleextendsHTMLElement{// The browser calls this method when an <inline-circle> element// is inserted into the document. There is also a disconnectedCallback()// that we don't need in this example.connectedCallback(){// Set the styles needed to create circlesthis.style.display="inline-block";this.style.borderRadius="50%";this.style.border="solid black 1px";this.style.transform="translateY(10%)";// If there is not already a size defined, set a default size// that is based on the current font size.if(!this.style.width){this.style.width="0.8em";this.style.height="0.8em";}}// The static observedAttributes property specifies which attributes// we want to be notified about changes to. (We use a getter here since// we can only use "static" with methods.)staticgetobservedAttributes(){return["diameter","color"];}// This callback is invoked when one of the attributes listed above// changes, either when the custom element is first parsed, or later.attributeChangedCallback(name,oldValue,newValue){switch(name){case"diameter":// If the diameter attribute changes, update the size stylesthis.style.width=newValue;this.style.height=newValue;break;case"color":// If the color attribute changes, update the color stylesthis.style.backgroundColor=newValue;break;}}// Define JavaScript properties that correspond to the element's// attributes. These getters and setters just get and set the underlying// attributes. If a JavaScript property is set, that sets the attribute// which triggers a call to attributeChangedCallback() which updates// the element styles.getdiameter(){returnthis.getAttribute("diameter");}setdiameter(diameter){this.setAttribute("diameter",diameter);}getcolor(){returnthis.getAttribute("color");}setcolor(color){this.setAttribute("color",color);}});
As already mentioned, elements in the shadow DOM are hidden from
regular DOM methods like querySelectorAll(). When a shadow root is
created and attached to its shadow host, it can be created in “open”
or “closed” mode. A closed shadow root is completely sealed away and
inaccessible. More commonly, though, shadow roots are created in
“open” mode, which means that the shadow host has a shadowRoot
property that JavaScript can use to gain access to the elements of
the shadow root, if it has some reason to do so.
Styles defined beneath a shadow root are private to that tree and will never affect the light DOM elements on the outside. (A shadow root can define default styles for its host element, but these will be overridden by light DOM styles.) Similarly, the light DOM styles that apply to the shadow host element have no effect on the descendants of the shadow root. Elements in the shadow DOM will inherit things like font size and background color from the light DOM, and styles in the shadow DOM can choose to use CSS variables defined in the light DOM. For the most part, however, the styles of the light DOM and the styles of the shadow DOM are completely independent: the author of a web component and the user of a web component do not have to worry about collisions or conflicts between their stylesheets. Being able to “scope” CSS in this way is perhaps the most important feature of the shadow DOM.
Some events (like “load”) that occur within the shadow DOM are
confined to the shadow DOM. Others, including focus, mouse, and
keyboard events bubble up and out. When an event that originates in
the shadow DOM crosses the boundary and begins to propagate in the
light DOM, its target property is changed to the shadow host
element, so it appears to have originated directly on that element.
The descendants of the shadow root are always displayed within the shadow host.
If those descendants include a <slot> element, then the regular
light DOM children of the host element are displayed as if they were
children of that <slot>, replacing any shadow DOM content in the
slot. If the shadow DOM does not include a <slot>, then any light
DOM content of the host is never displayed. If the shadow DOM has a
<slot>, but the shadow host has no light DOM children, then the
shadow DOM content of the slot is displayed as a default.
When light DOM content is displayed within a shadow DOM slot, we say
that those elements have been “distributed,” but it is important to
understand that the elements do not actually become part of the
shadow DOM. They can still be queried with querySelector(), and
they still appear in the light DOM as children or descendants of
the host element.
If the shadow DOM defines more than one <slot> and names those
slots with a name attribute, then children of the shadow host can
specify which slot they would like to appear in by specifying a
slot="slotname" attribute. We saw an example of this usage
in §15.6.1 when we demonstrated how to customize the
icons displayed by the <search-box> component.
/*** This class defines a custom HTML <search-box> element that displays an* <input> text input field plus two icons or emoji. By default, it displays a* magnifying glass emoji (indicating search) to the left of the text field* and an X emoji (indicating cancel) to the right of the text field. It* hides the border on the input field and displays a border around itself,* creating the appearance that the two emoji are inside the input* field. Similarly, when the internal input field is focused, the focus ring* is displayed around the <search-box>.** You can override the default icons by including <span> or <img> children* of <search-box> with slot="left" and slot="right" attributes.** <search-box> supports the normal HTML disabled and hidden attributes and* also size and placeholder attributes, which have the same meaning for this* element as they do for the <input> element.** Input events from the internal <input> element bubble up and appear with* their target field set to the <search-box> element.** The element fires a "search" event with the detail property set to the* current input string when the user clicks on the left emoji (the magnifying* glass). The "search" event is also dispatched when the internal text field* generates a "change" event (when the text has changed and the user types* Return or Tab).** The element fires a "clear" event when the user clicks on the right emoji* (the X). If no handler calls preventDefault() on the event then the element* clears the user's input once event dispatch is complete.** Note that there are no onsearch and onclear properties or attributes:* handlers for the "search" and "clear" events can only be registered with* addEventListener().*/classSearchBoxextendsHTMLElement{constructor(){super();// Invoke the superclass constructor; must be first.// Create a shadow DOM tree and attach it to this element, setting// the value of this.shadowRoot.this.attachShadow({mode:"open"});// Clone the template that defines the descendants and stylesheet for// this custom component, and append that content to the shadow root.this.shadowRoot.append(SearchBox.template.content.cloneNode(true));// Get references to the important elements in the shadow DOMthis.input=this.shadowRoot.querySelector("#input");letleftSlot=this.shadowRoot.querySelector('slot[name="left"]');letrightSlot=this.shadowRoot.querySelector('slot[name="right"]');// When the internal input field gets or loses focus, set or remove// the "focused" attribute which will cause our internal stylesheet// to display or hide a fake focus ring on the entire component. Note// that the "blur" and "focus" events bubble and appear to originate// from the <search-box>.this.input.onfocus=()=>{this.setAttribute("focused","");};this.input.onblur=()=>{this.removeAttribute("focused");};// If the user clicks on the magnifying glass, trigger a "search"// event. Also trigger it if the input field fires a "change"// event. (The "change" event does not bubble out of the Shadow DOM.)leftSlot.onclick=this.input.onchange=(event)=>{event.stopPropagation();// Prevent click events from bubblingif(this.disabled)return;// Do nothing when disabledthis.dispatchEvent(newCustomEvent("search",{detail:this.input.value}));};// If the user clicks on the X, trigger a "clear" event.// If preventDefault() is not called on the event, clear the input.rightSlot.onclick=(event)=>{event.stopPropagation();// Don't let the click bubble upif(this.disabled)return;// Don't do anything if disabledlete=newCustomEvent("clear",{cancelable:true});this.dispatchEvent(e);if(!e.defaultPrevented){// If the event was not "cancelled"this.input.value="";// then clear the input field}};}// When some of our attributes are set or changed, we need to set the// corresponding value on the internal <input> element. This life cycle// method, together with the static observedAttributes property below,// takes care of that.attributeChangedCallback(name,oldValue,newValue){if(name==="disabled"){this.input.disabled=newValue!==null;}elseif(name==="placeholder"){this.input.placeholder=newValue;}elseif(name==="size"){this.input.size=newValue;}elseif(name==="value"){this.input.value=newValue;}}// Finally, we define property getters and setters for properties that// correspond to the HTML attributes we support. The getters simply return// the value (or the presence) of the attribute. And the setters just set// the value (or the presence) of the attribute. When a setter method// changes an attribute, the browser will automatically invoke the// attributeChangedCallback above.getplaceholder(){returnthis.getAttribute("placeholder");}getsize(){returnthis.getAttribute("size");}getvalue(){returnthis.getAttribute("value");}getdisabled(){returnthis.hasAttribute("disabled");}gethidden(){returnthis.hasAttribute("hidden");}setplaceholder(value){this.setAttribute("placeholder",value);}setsize(value){this.setAttribute("size",value);}setvalue(text){this.setAttribute("value",text);}setdisabled(value){if(value)this.setAttribute("disabled","");elsethis.removeAttribute("disabled");}sethidden(value){if(value)this.setAttribute("hidden","");elsethis.removeAttribute("hidden");}}// This static field is required for the attributeChangedCallback method.// Only attributes named in this array will trigger calls to that method.SearchBox.observedAttributes=["disabled","placeholder","size","value"];// Create a <template> element to hold the stylesheet and the tree of// elements that we'll use for each instance of the SearchBox element.SearchBox.template=document.createElement("template");// We initialize the template by parsing this string of HTML. Note, however,// that when we instantiate a SearchBox, we are able to just clone the nodes// in the template and do have to parse the HTML again.SearchBox.template.innerHTML=`<style>/** The :host selector refers to the <search-box> element in the light* DOM. These styles are defaults and can be overridden by the user of the* <search-box> with styles in the light DOM.*/:host {display: inline-block; /* The default is inline display */border: solid black 1px; /* A rounded border around the <input> and <slots> */border-radius: 5px;padding: 4px 6px; /* And some space inside the border */}:host([hidden]) { /* Note the parentheses: when host has hidden... */display:none; /* ...attribute set don't display it */}:host([disabled]) { /* When host has the disabled attribute... */opacity: 0.5; /* ...gray it out */}:host([focused]) { /* When host has the focused attribute... */box-shadow: 0 0 2px 2px #6AE; /* display this fake focus ring. */}/* The rest of the stylesheet only applies to elements in the Shadow DOM. */input {border-width: 0; /* Hide the border of the internal input field. */outline: none; /* Hide the focus ring, too. */font: inherit; /* <input> elements don't inherit font by default */background: inherit; /* Same for background color. */}slot {cursor: default; /* An arrow pointer cursor over the buttons */user-select: none; /* Don't let the user select the emoji text */}</style><div><slot name="left">\u{1f50d}</slot> <!-- U+1F50D is a magnifying glass --><input type="text" id="input" /> <!-- The actual input element --><slot name="right">\u{2573}</slot> <!-- U+2573 is an X --></div>`;// Finally, we call customElement.define() to register the SearchBox element// as the implementation of the <search-box> tag. Custom elements are required// to have a tag name that contains a hyphen.customElements.define("search-box",SearchBox);
You can use .svg image files with regular HTML <img> tags,
just as you would use a .png or .jpeg image.
Because the XML-based SVG format is so similar to HTML, you can actually embed SVG tags directly into your HTML documents. If you do this, the browser’s HTML parser allows you to omit XML namespaces and treat SVG tags as if they were HTML tags.
You can use the DOM API to dynamically create SVG elements to generate images on demand.
<html><head><title>Analog Clock</title><style>/* These CSS styles all apply to the SVG elements defined below */#clock{/* Styles for everything in the clock:*/stroke:black;/* black lines */stroke-linecap:round;/* with rounded ends */fill:#ffe;/* on an off-white background */}#clock.face{stroke-width:3;}/* Clock face outline */#clock.ticks{stroke-width:2;}/* Lines that mark each hour */#clock.hands{stroke-width:3;}/* How to draw the clock hands */#clock.numbers{/* How to draw the numbers */font-family:sans-serif;font-size:10;font-weight:bold;text-anchor:middle;stroke:none;fill:black;}</style></head><body><svgid="clock"viewBox="0 0 100 100"width="250"height="250"><!-- The width and height attributes are the screen size of the graphic --><!-- The viewBox attribute gives the internal coordinate system --><circleclass="face"cx="50"cy="50"r="45"/><!-- the clock face --><gclass="ticks"><!-- tick marks for each of the 12 hours --><linex1='50'y1='5.000'x2='50.00'y2='10.00'/><linex1='72.50'y1='11.03'x2='70.00'y2='15.36'/><linex1='88.97'y1='27.50'x2='84.64'y2='30.00'/><linex1='95.00'y1='50.00'x2='90.00'y2='50.00'/><linex1='88.97'y1='72.50'x2='84.64'y2='70.00'/><linex1='72.50'y1='88.97'x2='70.00'y2='84.64'/><linex1='50.00'y1='95.00'x2='50.00'y2='90.00'/><linex1='27.50'y1='88.97'x2='30.00'y2='84.64'/><linex1='11.03'y1='72.50'x2='15.36'y2='70.00'/><linex1='5.000'y1='50.00'x2='10.00'y2='50.00'/><linex1='11.03'y1='27.50'x2='15.36'y2='30.00'/><linex1='27.50'y1='11.03'x2='30.00'y2='15.36'/></g><gclass="numbers"><!-- Number the cardinal directions--><textx="50"y="18">12</text><textx="85"y="53">3</text><textx="50"y="88">6</text><textx="15"y="53">9</text></g><gclass="hands"><!-- Draw hands pointing straight up. --><lineclass="hourhand"x1="50"y1="50"x2="50"y2="25"/><lineclass="minutehand"x1="50"y1="50"x2="50"y2="20"/></g></svg><scriptsrc="clock.js"></script></body></html>
(functionupdateClock(){// Update the SVG clock graphic to show current timeletnow=newDate();// Current timeletsec=now.getSeconds();// Secondsletmin=now.getMinutes()+sec/60;// Fractional minuteslethour=(now.getHours()%12)+min/60;// Fractional hoursletminangle=min*6;// 6 degrees per minutelethourangle=hour*30;// 30 degrees per hour// Get SVG elements for the hands of the clockletminhand=document.querySelector("#clock .minutehand");lethourhand=document.querySelector("#clock .hourhand");// Set an SVG attribute on them to move them around the clock faceminhand.setAttribute("transform",`rotate(${minangle},50,50)`);hourhand.setAttribute("transform",`rotate(${hourangle},50,50)`);// Run this function again in 10 secondssetTimeout(updateClock,10000);}());// Note immediate invocation of the function here.
/*** Create an <svg> element and draw a pie chart into it.** This function expects an object argument with the following properties:** width, height: the size of the SVG graphic, in pixels* cx, cy, r: the center and radius of the pie* lx, ly: the upper-left corner of the chart legend* data: an object whose property names are data labels and whose* property values are the values associated with each label** The function returns an <svg> element. The caller must insert it into* the document in order to make it visible.*/functionpieChart(options){let{width,height,cx,cy,r,lx,ly,data}=options;// This is the XML namespace for svg elementsletsvg="http://www.w3.org/2000/svg";// Create the <svg> element, and specify pixel size and user coordinatesletchart=document.createElementNS(svg,"svg");chart.setAttribute("width",width);chart.setAttribute("height",height);chart.setAttribute("viewBox",`0 0${width}${height}`);// Define the text styles we'll use for the chart. If we leave these// values unset here, they can be set with CSS instead.chart.setAttribute("font-family","sans-serif");chart.setAttribute("font-size","18");// Get labels and values as arrays and add up the values so we know how// big the pie is.letlabels=Object.keys(data);letvalues=Object.values(data);lettotal=values.reduce((x,y)=>x+y);// Figure out the angles for all the slices. Slice i starts at angles[i]// and ends at angles[i+1]. The angles are measured in radians.letangles=[0];values.forEach((x,i)=>angles.push(angles[i]+x/total*2*Math.PI));// Now loop through the slices of the pievalues.forEach((value,i)=>{// Compute the two points where our slice intersects the circle// These formulas are chosen so that an angle of 0 is at 12 o'clock// and positive angles increase clockwise.letx1=cx+r*Math.sin(angles[i]);lety1=cy-r*Math.cos(angles[i]);letx2=cx+r*Math.sin(angles[i+1]);lety2=cy-r*Math.cos(angles[i+1]);// This is a flag for angles larger than a half circle// It is required by the SVG arc drawing componentletbig=(angles[i+1]-angles[i]>Math.PI)?1:0;// This string describes how to draw a slice of the pie chart:letpath=`M${cx},${cy}`+// Move to circle center.`L${x1},${y1}`+// Draw line to (x1,y1).`A${r},${r}0${big}1`+// Draw an arc of radius r...`${x2},${y2}`+// ...ending at to (x2,y2)."Z";// Close path back to (cx,cy).// Compute the CSS color for this slice. This formula works for only// about 15 colors. So don't include more than 15 slices in a chart.letcolor=`hsl(${(i*40)%360},${90-3*i}%,${50+2*i}%)`;// We describe a slice with a <path> element. Note createElementNS().letslice=document.createElementNS(svg,"path");// Now set attributes on the <path> elementslice.setAttribute("d",path);// Set the path for this sliceslice.setAttribute("fill",color);// Set slice colorslice.setAttribute("stroke","black");// Outline slice in blackslice.setAttribute("stroke-width","1");// 1 CSS pixel thickchart.append(slice);// Add slice to chart// Now draw a little matching square for the keyleticon=document.createElementNS(svg,"rect");icon.setAttribute("x",lx);// Position the squareicon.setAttribute("y",ly+30*i);icon.setAttribute("width",20);// Size the squareicon.setAttribute("height",20);icon.setAttribute("fill",color);// Same fill color as sliceicon.setAttribute("stroke","black");// Same outline, too.icon.setAttribute("stroke-width","1");chart.append(icon);// Add to the chart// And add a label to the right of the rectangleletlabel=document.createElementNS(svg,"text");label.setAttribute("x",lx+30);// Position the textlabel.setAttribute("y",ly+30*i+16);label.append(`${labels[i]}${value}`);// Add text to labelchart.append(label);// Add label to the chart});returnchart;}
document.querySelector("#chart").append(pieChart({width:640,height:400,// Total size of the chartcx:200,cy:200,r:180,// Center and radius of the pielx:400,ly:10,// Position of the legenddata:{// The data to chart"JavaScript":71.5,"Java":45.4,"Bash/Shell":40.4,"Python":37.9,"C#":35.3,"PHP":31.4,"C++":24.6,"C":22.1,"TypeScript":18.3,"Ruby":10.3,"Swift":8.3,"Objective-C":7.3,"Go":7.2,}}));
<p>This is a red square:<canvasid="square"width=10height=10></canvas>.<p>This is a blue circle:<canvasid="circle"width=10height=10></canvas>.<script>letcanvas=document.querySelector("#square");// Get first canvas elementletcontext=canvas.getContext("2d");// Get 2D drawing contextcontext.fillStyle="#f00";// Set fill color to redcontext.fillRect(0,0,10,10);// Fill a squarecanvas=document.querySelector("#circle");// Second canvas elementcontext=canvas.getContext("2d");// Get its contextcontext.beginPath();// Begin a new "path"context.arc(5,5,5,0,2*Math.PI,true);// Add a circle to the pathcontext.fillStyle="#00f";// Set blue fill colorcontext.fill();// Fill the path</script>
letcanvas=document.querySelector("#my_canvas_id");letc=canvas.getContext('2d');
c.beginPath();// Start a new pathc.moveTo(100,100);// Begin a subpath at (100,100)c.lineTo(200,200);// Add a line from (100,100) to (200,200)c.lineTo(100,200);// Add a line from (200,200) to (100,200)
c.fill();// Fill a triangular areac.stroke();// Stroke two sides of the triangle
c.moveTo(300,100);// Begin a new subpath at (300,100);c.lineTo(300,200);// Draw a vertical line down to (300,200);
// Define a regular polygon with n sides, centered at (x,y) with radius r.// The vertices are equally spaced along the circumference of a circle.// Put the first vertex straight up or at the specified angle.// Rotate clockwise, unless the last argument is true.functionpolygon(c,n,x,y,r,angle=0,counterclockwise=false){c.moveTo(x+r*Math.sin(angle),// Begin a new subpath at the first vertexy-r*Math.cos(angle));// Use trigonometry to compute positionletdelta=2*Math.PI/n;// Angular distance between verticesfor(leti=1;i<n;i++){// For each of the remaining verticesangle+=counterclockwise?-delta:delta;// Adjust anglec.lineTo(x+r*Math.sin(angle),// Add line to next vertexy-r*Math.cos(angle));}c.closePath();// Connect last vertex back to the first}// Assume there is just one canvas, and get its context object to draw with.letc=document.querySelector("canvas").getContext("2d");// Start a new path and add polygon subpathsc.beginPath();polygon(c,3,50,70,50);// Trianglepolygon(c,4,150,60,50,Math.PI/4);// Squarepolygon(c,5,255,55,50);// Pentagonpolygon(c,6,365,53,50,Math.PI/6);// Hexagonpolygon(c,4,365,53,20,Math.PI/4,true);// Small square inside the hexagon// Set some properties that control how the graphics will lookc.fillStyle="#ccc";// Light gray interiorsc.strokeStyle="#008";// outlined with dark blue linesc.lineWidth=5;// five pixels wide.// Now draw all the polygons (each in its own subpath) with these callsc.fill();// Fill the shapesc.stroke();// And stroke their outlines
c.setLineDash([18,3,3,3]);// 18px dash, 3px space, 3px dot, 3px space
// A linear gradient, diagonally across the canvas (assuming no transforms)letbgfade=c.createLinearGradient(0,0,canvas.width,canvas.height);bgfade.addColorStop(0.0,"#88f");// Start with light blue in upper leftbgfade.addColorStop(1.0,"#fff");// Fade to white in lower right// A gradient between two concentric circles. Transparent in the middle// fading to translucent gray and then back to transparent.letdonut=c.createRadialGradient(300,300,100,300,300,300);donut.addColorStop(0.0,"transparent");// Transparentdonut.addColorStop(0.7,"rgba(100,100,100,.9)");// Translucent graydonut.addColorStop(1.0,"rgba(0,0,0,0)");// Transparent again
arc()This method adds a circle, or a portion of a circle (an arc), to the path. The arc to be drawn is specified with six parameters: the x and y coordinates of the center of a circle, the radius of the circle, the start and end angles of the arc, and the direction (clockwise or counterclockwise) of the arc between those two angles. If there is a current point in the path, then this method connects the current point to the beginning of the arc with a straight line (which is useful when drawing wedges or pie slices), then connects the beginning of the arc to the end of the arc with a portion of a circle, leaving the end of the arc as the new current point. If there is no current point when this method is called, then it only adds the circular arc to the path.
ellipse()This method is much like arc() except that it adds an
ellipse or a portion of an ellipse to the path. Instead of one
radius, it has two: an x-axis radius and a y-axis radius. Also,
because ellipses are not radially symmetrical, this method takes
another argument that specifies the number of radians by which the
ellipse is rotated clockwise about its center.
arcTo()This method draws a straight line and a circular arc just
like the arc() method does, but it specifies the arc to be drawn
using different parameters. The arguments to arcTo() specify
points P1 and P2 and a radius. The arc that is added to the path has the specified radius. It begins at the tangent point with the (imaginary) line from the current point to P1 and ends at the tangent point with the (imaginary) line between P1 and P2. This
unusual-seeming method of specifying arcs is actually quite useful
for drawing shapes with rounded corners. If you specify a radius of
0, this method just draws a straight line from the current point to
P1. With a nonzero radius, however, it draws a straight line from
the current point in the direction of P1, then curves that line
around in a circle until it is heading in the direction of P2.
bezierCurveTo()This method adds a new point P to the subpath and connects it to the current point with a cubic Bezier curve. The shape of the curve is specified by two “control points,” C1 and C2. At the start of the curve (at the current point), the curve heads in the direction of C1. At the end of the curve (at point P), the curve arrives from the direction of C2. In between these points, the direction of the curve varies smoothly. The point P becomes the new current point for the subpath.
quadraticCurveTo()This method is like bezierCurveTo(), but it
uses a quadratic Bezier curve instead of a cubic Bezier curve and
has only a single control point.
// A utility function to convert angles from degrees to radiansfunctionrads(x){returnMath.PI*x/180;}// Get the context object of the document's canvas elementletc=document.querySelector("canvas").getContext("2d");// Define some graphics attributes and draw the curvesc.fillStyle="#aaa";// Gray fillsc.lineWidth=2;// 2-pixel black (by default) lines// Draw a circle.// There is no current point, so draw just the circle with no straight// line from the current point to the start of the circle.c.beginPath();c.arc(75,100,50,// Center at (75,100), radius 500,rads(360),false);// Go clockwise from 0 to 360 degreesc.fill();// Fill the circlec.stroke();// Stroke its outline.// Now draw an ellipse in the same wayc.beginPath();// Start new path not connected to the circlec.ellipse(200,100,50,35,rads(15),// Center, radii, and rotation0,rads(360),false);// Start angle, end angle, direction// Draw a wedge. Angles are measured clockwise from the positive x axis.// Note that arc() adds a line from the current point to the arc start.c.moveTo(325,100);// Start at the center of the circle.c.arc(325,100,50,// Circle center and radiusrads(-60),rads(0),// Start at angle -60 and go to angle 0true);// counterclockwisec.closePath();// Add radius back to the center of the circle// Similar wedge, offset a bit, and in the opposite directionc.moveTo(340,92);c.arc(340,92,42,rads(-60),rads(0),false);c.closePath();// Use arcTo() for rounded corners. Here we draw a square with// upper left corner at (400,50) and corners of varying radii.c.moveTo(450,50);// Begin in the middle of the top edge.c.arcTo(500,50,500,150,30);// Add part of top edge and upper right corner.c.arcTo(500,150,400,150,20);// Add right edge and lower right corner.c.arcTo(400,150,400,50,10);// Add bottom edge and lower left corner.c.arcTo(400,50,500,50,0);// Add left edge and upper left corner.c.closePath();// Close path to add the rest of the top edge.// Quadratic Bezier curve: one control pointc.moveTo(525,125);// Begin herec.quadraticCurveTo(550,75,625,125);// Draw a curve to (625, 125)c.fillRect(550-3,75-3,6,6);// Mark the control point (550,75)// Cubic Bezier curvec.moveTo(625,100);// Start at (625, 100)c.bezierCurveTo(645,70,705,130,725,100);// Curve to (725, 100)c.fillRect(645-3,70-3,6,6);// Mark control pointsc.fillRect(705-3,130-3,6,6);// Finally, fill the curves and stroke their outlines.c.fill();c.stroke();
letwidth=c.measureText(text).width;
letimg=document.createElement("img");// Create an <img> elementimg.src=canvas.toDataURL();// Set its src attributedocument.body.appendChild(img);// Append it to the document
x' = x + dx; // An X coordinate of 0 in the new system is dx in the oldy'=y+dy;
x' = sx * x;y'=sy*y;
x' = x * cos(a) - y * sin(a);y'=y*cos(a)+x*sin(a);
x''=sx*x+dx;y''=sy*y+dy;
x''=sx*(x+dx);y''=sy*(y+dy);
x' = ax + cy + ey'=bx+dy+f
// Shear transform:// x' = x + kx*y;// y' = ky*x + y;functionshear(c,kx,ky){c.transform(1,ky,kx,1,0,0);}// Rotate theta radians counterclockwise around the point (x,y)// This can also be accomplished with a translate, rotate, translate sequencefunctionrotateAbout(c,theta,x,y){letct=Math.cos(theta);letst=Math.sin(theta);c.transform(ct,-st,st,ct,-x*ct-y*st+x,x*st-y*ct+y);}
c.save();// Save current coordinate systemc.setTransform(1,0,0,1,0,0);// Revert to the default coordinate system// Perform operations using default CSS pixel coordinatesc.restore();// Restore the saved coordinate system
c.lineTo(len,0);
letdeg=Math.PI/180;// For converting degrees to radians// Draw a level-n Koch snowflake fractal on the canvas context c,// with lower-left corner at (x,y) and side length len.functionsnowflake(c,n,x,y,len){c.save();// Save current transformationc.translate(x,y);// Translate origin to starting pointc.moveTo(0,0);// Begin a new subpath at the new originleg(n);// Draw the first leg of the snowflakec.rotate(-120*deg);// Now rotate 120 degrees counterclockwiseleg(n);// Draw the second legc.rotate(-120*deg);// Rotate againleg(n);// Draw the final legc.closePath();// Close the subpathc.restore();// And restore original transformation// Draw a single leg of a level-n Koch snowflake.// This function leaves the current point at the end of the leg it has// drawn and translates the coordinate system so the current point is (0,0).// This means you can easily call rotate() after drawing a leg.functionleg(n){c.save();// Save the current transformationif(n===0){// Nonrecursive case:c.lineTo(len,0);// Just draw a horizontal line}// _ _else{// Recursive case: draw 4 sub-legs like: \/c.scale(1/3,1/3);// Sub-legs are 1/3 the size of this legleg(n-1);// Recurse for the first sub-legc.rotate(60*deg);// Turn 60 degrees clockwiseleg(n-1);// Second sub-legc.rotate(-120*deg);// Rotate 120 degrees backleg(n-1);// Third sub-legc.rotate(60*deg);// Rotate back to our original headingleg(n-1);// Final sub-leg}c.restore();// Restore the transformationc.translate(len,0);// But translate to make end of leg (0,0)}}letc=document.querySelector("canvas").getContext("2d");snowflake(c,0,25,125,125);// A level-0 snowflake is a trianglesnowflake(c,1,175,125,125);// A level-1 snowflake is a 6-sided starsnowflake(c,2,325,125,125);// etc.snowflake(c,3,475,125,125);snowflake(c,4,625,125,125);// A level-4 snowflake looks like a snowflake!c.stroke();// Stroke this very complicated path
// Define some drawing attributesc.font="bold 60pt sans-serif";// Big fontc.lineWidth=2;// Narrow linesc.strokeStyle="#000";// Black lines// Outline a rectangle and some textc.strokeRect(175,25,50,325);// A vertical stripe down the middlec.strokeText("<canvas>",15,330);// Note strokeText() instead of fillText()// Define a complex path with an interior that is outside.polygon(c,3,200,225,200);// Large trianglepolygon(c,3,200,225,100,0,true);// Smaller reverse triangle inside// Make that path the clipping region.c.clip();// Stroke the path with a 5 pixel line, entirely inside the clipping region.c.lineWidth=10;// Half of this 10 pixel line will be clipped awayc.stroke();// Fill the parts of the rectangle and text that are inside the clipping regionc.fillStyle="#aaa";// Light grayc.fillRect(175,25,50,325);// Fill the vertical stripec.fillStyle="#888";// Darker grayc.fillText("<canvas>",15,330);// Fill the text
// Smear the pixels of the rectangle to the right, producing a// sort of motion blur as if objects are moving from right to left.// n must be 2 or larger. Larger values produce bigger smears.// The rectangle is specified in the default coordinate system.functionsmear(c,n,x,y,w,h){// Get the ImageData object that represents the rectangle of pixels to smearletpixels=c.getImageData(x,y,w,h);// This smear is done in-place and requires only the source ImageData.// Some image processing algorithms require an additional ImageData to// store transformed pixel values. If we needed an output buffer, we could// create a new ImageData with the same dimensions like this:// let output_pixels = c.createImageData(pixels);// Get the dimensions of the grid of pixels in the ImageData objectletwidth=pixels.width,height=pixels.height;// This is the byte array that holds the raw pixel data, left-to-right and// top-to-bottom. Each pixel occupies 4 consecutive bytes in R,G,B,A order.letdata=pixels.data;// Each pixel after the first in each row is smeared by replacing it with// 1/nth of its own value plus m/nths of the previous pixel's valueletm=n-1;for(letrow=0;row<height;row++){// For each rowleti=row*width*4+4;// The offset of the second pixel of the rowfor(letcol=1;col<width;col++,i+=4){// For each columndata[i]=(data[i]+data[i-4]*m)/n;// Red pixel componentdata[i+1]=(data[i+1]+data[i-3]*m)/n;// Greendata[i+2]=(data[i+2]+data[i-2]*m)/n;// Bluedata[i+3]=(data[i+3]+data[i-1]*m)/n;// Alpha component}}// Now copy the smeared image data back to the same position on the canvasc.putImageData(pixels,x,y);}
// Load the sound effect in advance so it is ready for useletsoundeffect=newAudio("soundeffect.mp3");// Play the sound effect whenever the user clicks the mouse buttondocument.addEventListener("click",()=>{soundeffect.cloneNode().play();// Load and play the sound});
// Begin by creating an audioContext object. Safari still requires// us to use webkitAudioContext instead of AudioContext.letaudioContext=new(this.AudioContext||this.webkitAudioContext)();// Define the base sound as a combination of three pure sine wavesletnotes=[293.7,370.0,440.0];// D major chord: D, F# and A// Create oscillator nodes for each of the notes we want to playletoscillators=notes.map(note=>{leto=audioContext.createOscillator();o.frequency.value=note;returno;});// Shape the sound by controlling its volume over time.// Starting at time 0 quickly ramp up to full volume.// Then starting at time 0.1 slowly ramp down to 0.letvolumeControl=audioContext.createGain();volumeControl.gain.setTargetAtTime(1,0.0,0.02);volumeControl.gain.setTargetAtTime(0,0.1,0.2);// We're going to send the sound to the default destination:// the user's speakersletspeakers=audioContext.destination;// Connect each of the source notes to the volume controloscillators.forEach(o=>o.connect(volumeControl));// And connect the output of the volume control to the speakers.volumeControl.connect(speakers);// Now start playing the sounds and let them run for 1.25 seconds.letstartTime=audioContext.currentTime;letstopTime=startTime+1.25;oscillators.forEach(o=>{o.start(startTime);o.stop(stopTime);});// If we want to create a sequence of sounds we can use event handlersoscillators[0].addEventListener("ended",()=>{// This event handler is invoked when the note stops playing});
The fetch() method defines a Promise-based API for making HTTP and HTTPS
requests. The fetch() API makes basic GET requests simple but has
a comprehensive feature set that also supports just about any
possible HTTP use case.
The Server-Sent Events (or SSE) API is a convenient, event-based interface to HTTP “long polling” techniques where the web server holds the network connection open so that it can send data to the client whenever it wants.
WebSockets is a networking protocol that is not HTTP but is designed to interoperate with HTTP. It defines an asynchronous message-passing API where clients and servers can send and receive messages from each other in a way that is similar to TCP network sockets.
Call fetch(), passing the URL whose content you want to retrieve.
Get the response object that is asynchronously returned by step 1 when the HTTP response begins to arrive and call a method of this response object to ask for the body of the response.
Get the body object that is asynchronously returned by step 2 and process it however you want.
fetch("/api/users/current")// Make an HTTP (or HTTPS) GET request.then(response=>response.json())// Parse its body as a JSON object.then(currentUser=>{// Then process that parsed objectdisplayUserInfo(currentUser);});
asyncfunctionisServiceReady(){letresponse=awaitfetch("/api/service/status");letbody=awaitresponse.text();returnbody==="ready";}
fetch("/api/users/current")// Make an HTTP (or HTTPS) GET request..then(response=>{// When we get a response, first check itif(response.ok&&// for a success code and the expected type.response.headers.get("Content-Type")==="application/json"){returnresponse.json();// Return a Promise for the body.}else{thrownewError(// Or throw an error.`Unexpected response status${response.status}or content type`);}}).then(currentUser=>{// When the response.json() Promise resolvesdisplayUserInfo(currentUser);// do something with the parsed body.}).catch(error=>{// Or if anything went wrong, just log the error.// If the user's browser is offline, fetch() itself will reject.// If the server returns a bad response then we throw an error above.console.log("Error while fetching current user:",error);});
fetch(url).then(response=>{for(let[name,value]ofresponse.headers){console.log(`${name}:${value}`);}});
asyncfunctionsearch(term){leturl=newURL("/api/search");url.searchParams.set("q",term);letresponse=awaitfetch(url);if(!response.ok)thrownewError(response.statusText);letresultsArray=awaitresponse.json();returnresultsArray;}
letauthHeaders=newHeaders();// Don't use Basic auth unless it is over an HTTPS connection.authHeaders.set("Authorization",`Basic${btoa(`${username}:${password}`)}`);fetch("/api/users/",{headers:authHeaders}).then(response=>response.json())// Error handling omitted....then(usersList=>displayAllUsers(usersList));
letrequest=newRequest(url,{headers});fetch(request).then(response=>...);
arrayBuffer()This method returns a Promise that resolves to an ArrayBuffer. This is useful when the response contains binary data. You can use the ArrayBuffer to create a typed array (§11.2) or a DataView object (§11.2.5) from which you can read the binary data.
blob()This method returns a Promise that resolves to a Blob
object. Blobs are not covered in any detail in this book, but the name
stands for “Binary Large Object,” and they are useful when you expect
large amounts of binary data. If you ask for the body of the response as
a Blob, the browser implementation may stream the response data to a
temporary file and then return a Blob object that represents that
temporary file. Blob objects, therefore, do not allow random access to
the response body the way that an ArrayBuffer does. Once you have a
Blob, you can create a URL that refers to it with
URL.createObjectURL(), or you can use the event-based FileReader API
to asynchronously obtain the content of the Blob as a string or an
ArrayBuffer. At the time of this writing, some browsers also define
Promise-based text() and arrayBuffer() methods that give a more
direct route for obtaining the content of a Blob.
formData()This method returns a Promise that resolves to a FormData object. You should use this method if you expect the body of the Response to be encoded in “multipart/form-data” format. This format is common in POST requests made to a server, but uncommon in server responses, so this method is not frequently used.
fetch('big.json').then(response=>streamBody(response,updateProgress)).then(bodyText=>JSON.parse(bodyText)).then(handleBigJSONObject);
/*** An asynchronous function for streaming the body of a Response object* obtained from a fetch() request. Pass the Response object as the first* argument followed by two optional callbacks.** If you specify a function as the second argument, that reportProgress* callback will be called once for each chunk that is received. The first* argument passed is the total number of bytes received so far. The second* argument is a number between 0 and 1 specifying how complete the download* is. If the Response object has no "Content-Length" header, however, then* this second argument will always be NaN.** If you want to process the data in chunks as they arrive, specify a* function as the third argument. The chunks will be passed, as Uint8Array* objects, to this processChunk callback.** streamBody() returns a Promise that resolves to a string. If a processChunk* callback was supplied then this string is the concatenation of the values* returned by that callback. Otherwise the string is the concatenation of* the chunk values converted to UTF-8 strings.*/asyncfunctionstreamBody(response,reportProgress,processChunk){// How many bytes are we expecting, or NaN if no headerletexpectedBytes=parseInt(response.headers.get("Content-Length"));letbytesRead=0;// How many bytes received so farletreader=response.body.getReader();// Read bytes with this functionletdecoder=newTextDecoder("utf-8");// For converting bytes to textletbody="";// Text read so farwhile(true){// Loop until we exit belowlet{done,value}=awaitreader.read();// Read a chunkif(value){// If we got a byte array:if(processChunk){// Process the bytes ifletprocessed=processChunk(value);// a callback was passed.if(processed){body+=processed;}}else{// Otherwise, convert bytesbody+=decoder.decode(value,{stream:true});// to text.}if(reportProgress){// If a progress callback wasbytesRead+=value.length;// passed, then call itreportProgress(bytesRead,bytesRead/expectedBytes);}}if(done){// If this is the last chunk,break;// exit the loop}}returnbody;// Return the body text we accumulated}
fetch(url,{method:"POST"}).then(r=>r.json()).then(handleResponse);
fetch(url,{method:"POST",body:"hello world"})
fetch(url,{method:"POST",headers:newHeaders({"Content-Type":"application/json"}),body:JSON.stringify(requestBody)})
You can specify your parameter names and values with URLSearchParams
(which we saw earlier in this section, and which is documented in
§11.9) and then pass the URLSearchParams object as the value of
the body property. If you do this, the body will be set to a string
that looks like the query portion of a URL, and the “Content-Type”
header will be automatically set to
“application/x-www-form-urlencoded;charset=UTF-8.”
If instead you specify your parameter names and values with a
FormData object, the body will use a more verbose multipart encoding
and “Content-Type” will be set to “multipart/form-data; boundary=…”
with a unique boundary string that matches the body. Using a FormData
object is particularly useful when the values you want to upload are
long, or are File or Blob objects that may each have its own
“Content-Type.” FormData objects can be created and initialized with
values by passing a <form> element to the FormData()
constructor. But you can also create “multipart/form-data” request
bodies by invoking the FormData() constructor with no arguments and
initializing the name/value pairs it represents with the set() and
append() methods.
// The canvas.toBlob() function is callback-based.// This is a Promise-based wrapper for it.asyncfunctiongetCanvasBlob(canvas){returnnewPromise((resolve,reject)=>{canvas.toBlob(resolve);});}// Here is how we upload a PNG file from a canvasasyncfunctionuploadCanvasImage(canvas){letpngblob=awaitgetCanvasBlob(canvas);letformdata=newFormData();formdata.set("canvasimage",pngblob);letresponse=awaitfetch("/upload",{method:"POST",body:formdata});letbody=awaitresponse.json();}
// This function is like fetch(), but it adds support for a timeout// property in the options object and aborts the fetch if it is not complete// within the number of milliseconds specified by that property.functionfetchWithTimeout(url,options={}){if(options.timeout){// If the timeout property exists and is nonzeroletcontroller=newAbortController();// Create a controlleroptions.signal=controller.signal;// Set the signal property// Start a timer that will send the abort signal after the specified// number of milliseconds have passed. Note that we never cancel// this timer. Calling abort() after the fetch is complete has// no effect.setTimeout(()=>{controller.abort();},options.timeout);}// Now just perform a normal fetchreturnfetch(url,options);}
cacheUse this property to override the browser’s default caching
behavior. HTTP caching is a complex topic that is beyond the scope of
this book, but if you know something about how it works, you can use the
following legal values of cache:
"default"This value specifies the default caching behavior. Fresh responses in the cache are served directly from the cache, and stale responses are revalidated before being served.
"no-store"This value makes the browser ignore its cache. The cache is not checked for matches when the request is made and is not updated when the response arrives.
"reload"This value tells the browser to always make a normal network request, ignoring the cache. When the response arrives, however, it is stored in the cache.
"no-cache"This (misleadingly named) value tells the browser to not serve fresh values from the cache. Fresh or stale cached values are revalidated before being returned.
"force-cache"This value tells the browser to serve responses from the cache even if they are stale.
redirectThis property controls how the browser handles redirect responses from the server. The three legal values are:
"follow"This is the default value, and it makes the browser follow
redirects automatically. If you use this default, the Response objects you
get with fetch() should never have a status in the 300 to 399
range.
"error"This value makes fetch() reject its returned Promise if
the server returns a redirect response.
"manual"This value means that you want to manually handle redirect
responses, and the Promise returned by fetch() may resolve to a
Response object with a status in the 300 to 399 range. In this
case, you will have to use the “Location” header of the Response to
manually follow the redirection.
referrerYou can set this property to a string that contains a relative URL to specify the value of the HTTP “Referer” header (which is historically misspelled with three Rs instead of four). If you set this property to the empty string, then the “Referer” header will be omitted from the request.
letticker=newEventSource("stockprices.php");ticker.addEventListener("bid",(event)=>{displayNewBid(event.data);}
event: bid // sets the type of the event object
data: GOOG // sets the data property
data: 999 // appends a newline and more data
// a blank line marks the end of the event
<html>
<head><title>SSE Chat</title></head>
<body>
<!-- The chat UI is just a single text input field -->
<!-- New chat messages will be inserted before this input field -->
<input id="input" style="width:100%; padding:10px; border:solid black 2px"/>
<script>
// Take care of some UI details
let nick = prompt("Enter your nickname"); // Get user's nickname
let input = document.getElementById("input"); // Find the input field
input.focus(); // Set keyboard focus
// Register for notification of new messages using EventSource
let chat = new EventSource("/chat");
chat.addEventListener("chat", event => { // When a chat message arrives
let div = document.createElement("div"); // Create a <div>
div.append(event.data); // Add text from the message
input.before(div); // And add div before input
input.scrollIntoView(); // Ensure input elt is visible
});
// Post the user's messages to the server using fetch
input.addEventListener("change", ()=>{ // When the user strikes return
fetch("/chat", { // Start an HTTP request to this url.
method: "POST", // Make it a POST request with body
body: nick + ": " + input.value // set to the user's nick and input.
})
.catch(e => console.error); // Ignore response, but log any errors.
input.value = ""; // Clear the input
});
</script>
</body>
</html>// This is server-side JavaScript, intended to be run with NodeJS.// It implements a very simple, completely anonymous chat room.// POST new messages to /chat, or GET a text/event-stream of messages// from the same URL. Making a GET request to / returns a simple HTML file// that contains the client-side chat UI.consthttp=require("http");constfs=require("fs");consturl=require("url");// The HTML file for the chat client. Used below.constclientHTML=fs.readFileSync("chatClient.html");// An array of ServerResponse objects that we're going to send events toletclients=[];// Create a new server, and listen on port 8080.// Connect to http://localhost:8080/ to use it.letserver=newhttp.Server();server.listen(8080);// When the server gets a new request, run this functionserver.on("request",(request,response)=>{// Parse the requested URLletpathname=url.parse(request.url).pathname;// If the request was for "/", send the client-side chat UI.if(pathname==="/"){// A request for the chat UIresponse.writeHead(200,{"Content-Type":"text/html"}).end(clientHTML);}// Otherwise send a 404 error for any path other than "/chat" or for// any method other than "GET" and "POST"elseif(pathname!=="/chat"||(request.method!=="GET"&&request.method!=="POST")){response.writeHead(404).end();}// If the /chat request was a GET, then a client is connecting.elseif(request.method==="GET"){acceptNewClient(request,response);}// Otherwise the /chat request is a POST of a new messageelse{broadcastNewMessage(request,response);}});// This handles GET requests for the /chat endpoint which are generated when// the client creates a new EventSource object (or when the EventSource// reconnects automatically).functionacceptNewClient(request,response){// Remember the response object so we can send future messages to itclients.push(response);// If the client closes the connection, remove the corresponding// response object from the array of active clientsrequest.connection.on("end",()=>{clients.splice(clients.indexOf(response),1);response.end();});// Set headers and send an initial chat event to just this one clientresponse.writeHead(200,{"Content-Type":"text/event-stream","Connection":"keep-alive","Cache-Control":"no-cache"});response.write("event: chat\ndata: Connected\n\n");// Note that we intentionally do not call response.end() here.// Keeping the connection open is what makes Server-Sent Events work.}// This function is called in response to POST requests to the /chat endpoint// which clients send when users type a new message.asyncfunctionbroadcastNewMessage(request,response){// First, read the body of the request to get the user's messagerequest.setEncoding("utf8");letbody="";forawait(letchunkofrequest){body+=chunk;}// Once we've read the body send an empty response and close the connectionresponse.writeHead(200).end();// Format the message in text/event-stream format, prefixing each// line with "data: "letmessage="data: "+body.replace("\n","\ndata: ");// Give the message data a prefix that defines it as a "chat" event// and give it a double newline suffix that marks the end of the event.letevent=`event: chat\n${message}\n\n`;// Now send this event to all listening clientsclients.forEach(client=>client.write(event));}
letsocket=newWebSocket("wss://example.com/stockticker");
WebSocket.CONNECTINGThis WebSocket is connecting.
WebSocket.OPENThis WebSocket is connected and ready for communication.
WebSocket.CLOSINGThis WebSocket connection is being closed.
WebSocket.CLOSEDThis WebSocket has been closed; no further communication is possible. This state can also occur when the initial connection attempt fails.
The Web Storage API consists of the localStorage and
sessionStorage objects, which are essentially persistent objects
that map string keys to string values. Web Storage is very easy to
use and is suitable for storing large (but not huge) amounts of
data.
Cookies are an old client-side storage mechanism that was designed for use by server-side scripts. An awkward JavaScript API makes cookies scriptable on the client side, but they’re hard to use and suitable only for storing small amounts of textual data. Also, any data stored as cookies is always transmitted to the server with every HTTP request, even if the data is only of interest to the client.
IndexedDB is an asynchronous API to an object database that supports indexing.
The property values of Storage objects must be strings.
The properties stored in a Storage object persist. If you set a property of the localStorage object and then the user reloads the page, the value you saved in that property is still available to your program.
letname=localStorage.username;// Query a stored value.if(!name){name=prompt("What is your name?");// Ask the user a question.localStorage.username=name;// Store the user's response.}
localStorage.clear();
// If you store a number, it is automatically converted to a string.// Don't forget to parse it when retrieving it from storage.localStorage.x=10;letx=parseInt(localStorage.x);// Convert a Date to a string when setting, and parse it when gettinglocalStorage.lastRead=(newDate()).toUTCString();letlastRead=newDate(Date.parse(localStorage.lastRead));// JSON makes a convenient encoding for any primitive or data structurelocalStorage.data=JSON.stringify(data);// Encode and storeletdata=JSON.parse(localStorage.data);// Retrieve and decode.
keyThe name or key of the item that was set or removed. If the
clear() method was called, this property will be null.
newValueHolds the new value of the item, if there is one. If
removeItem() was called, this property will not be present.
oldValueHolds the old value of an existing item that changed or was deleted. If a new property (with no old value) is added, then this property will not be present in the event object.
storageAreaThe Storage object that changed. This is usually the
localStorage object.
urlThe URL (as a string) of the document whose script made this storage change.
// Return the document's cookies as a Map object.// Assume that cookie values are encoded with encodeURIComponent().functiongetCookies(){letcookies=newMap();// The object we will returnletall=document.cookie;// Get all cookies in one big stringletlist=all.split("; ");// Split into individual name/value pairsfor(letcookieoflist){// For each cookie in that listif(!cookie.includes("="))continue;// Skip if there is no = signletp=cookie.indexOf("=");// Find the first = signletname=cookie.substring(0,p);// Get cookie nameletvalue=cookie.substring(p+1);// Get cookie valuevalue=decodeURIComponent(value);// Decode the valuecookies.set(name,value);// Remember cookie name and value}returncookies;}
document.cookie=`version=${encodeURIComponent(document.lastModified)}`;
// Store the name/value pair as a cookie, encoding the value with// encodeURIComponent() in order to escape semicolons, commas, and spaces.// If daysToLive is a number, set the max-age attribute so that the cookie// expires after the specified number of days. Pass 0 to delete a cookie.functionsetCookie(name,value,daysToLive=null){letcookie=`${name}=${encodeURIComponent(value)}`;if(daysToLive!==null){cookie+=`; max-age=${daysToLive*60*60*24}`;}document.cookie=cookie;}
// This utility function asynchronously obtains the database object (creating// and initializing the DB if necessary) and passes it to the callback.functionwithDB(callback){letrequest=indexedDB.open("zipcodes",1);// Request v1 of the databaserequest.onerror=console.error;// Log any errorsrequest.onsuccess=()=>{// Or call this when doneletdb=request.result;// The result of the request is the databasecallback(db);// Invoke the callback with the database};// If version 1 of the database does not yet exist, then this event// handler will be triggered. This is used to create and initialize// object stores and indexes when the DB is first created or to modify// them when we switch from one version of the DB schema to another.request.onupgradeneeded=()=>{initdb(request.result,callback);};}// withDB() calls this function if the database has not been initialized yet.// We set up the database and populate it with data, then pass the database to// the callback function.//// Our zip code database includes one object store that holds objects like this://// {// zipcode: "02134",// city: "Allston",// state: "MA",// }//// We use the "zipcode" property as the database key and create an index for// the city name.functioninitdb(db,callback){// Create the object store, specifying a name for the store and// an options object that includes the "key path" specifying the// property name of the key field for this store.letstore=db.createObjectStore("zipcodes",// store name{keyPath:"zipcode"});// Now index the object store by city name as well as by zip code.// With this method the key path string is passed directly as a// required argument rather than as part of an options object.store.createIndex("cities","city");// Now get the data we are going to initialize the database with.// The zipcodes.json data file was generated from CC-licensed data from// www.geonames.org: https://download.geonames.org/export/zip/US.zipfetch("zipcodes.json")// Make an HTTP GET request.then(response=>response.json())// Parse the body as JSON.then(zipcodes=>{// Get 40K zip code records// In order to insert zip code data into the database we need a// transaction object. To create our transaction object, we need// to specify which object stores we'll be using (we only have// one) and we need to tell it that we'll be doing writes to the// database, not just reads:lettransaction=db.transaction(["zipcodes"],"readwrite");transaction.onerror=console.error;// Get our object store from the transactionletstore=transaction.objectStore("zipcodes");// The best part about the IndexedDB API is that object stores// are *really* simple. Here's how we add (or update) our records:for(letrecordofzipcodes){store.put(record);}// When the transaction completes successfully, the database// is initialized and ready for use, so we can call the// callback function that was originally passed to withDB()transaction.oncomplete=()=>{callback(db);};});}// Given a zip code, use the IndexedDB API to asynchronously look up the city// with that zip code, and pass it to the specified callback, or pass null if// no city is found.functionlookupCity(zip,callback){withDB(db=>{// Create a read-only transaction object for this query. The// argument is an array of object stores we will need to use.lettransaction=db.transaction(["zipcodes"]);// Get the object store from the transactionletzipcodes=transaction.objectStore("zipcodes");// Now request the object that matches the specified zipcode key.// The lines above were synchronous, but this one is async.letrequest=zipcodes.get(zip);request.onerror=console.error;// Log errorsrequest.onsuccess=()=>{// Or call this function on successletrecord=request.result;// This is the query resultif(record){// If we found a match, pass it to the callbackcallback(`${record.city},${record.state}`);}else{// Otherwise, tell the callback that we failedcallback(null);}};});}// Given the name of a city, use the IndexedDB API to asynchronously// look up all zip code records for all cities (in any state) that have// that (case-sensitive) name.functionlookupZipcodes(city,callback){withDB(db=>{// As above, we create a transaction and get the object storelettransaction=db.transaction(["zipcodes"]);letstore=transaction.objectStore("zipcodes");// This time we also get the city index of the object storeletindex=store.index("cities");// Ask for all matching records in the index with the specified// city name, and when we get them we pass them to the callback.// If we expected more results, we might use openCursor() instead.letrequest=index.getAll(city);request.onerror=console.error;request.onsuccess=()=>{callback(request.result);};});}
letdataCruncher=newWorker("utils/cruncher.js");
dataCruncher.postMessage("/api/data/to/crunch");
dataCruncher.onmessage=function(e){letstats=e.data;// The message is the data property of the eventconsole.log(`Average:${stats.mean}`);}
self is a reference to the global object itself. WorkerGlobalScope
is not a Window object and does not define a window property.
The timer methods setTimeout(), clearTimeout(), setInterval(),
and clearInterval().
A location property that describes the URL that was passed to the
Worker() constructor. This property refers to a Location object, just
as the location property of a Window does. The Location object has
properties href, protocol, host, hostname, port, pathname,
search, and hash. In a worker, these properties are read-only,
however.
A navigator property that refers to an object with properties like
those of the Navigator object of a window. A worker’s Navigator object
has the properties appName, appVersion, platform, userAgent, and
onLine.
The usual event target methods addEventListener() and
removeEventListener().
// Before we start working, load the classes and utilities we'll needimportScripts("utils/Histogram.js","utils/BitSet.js");
// Handle uncaught worker errors with a handler inside the worker.self.onerror=function(e){console.log(`Error in worker at${e.filename}:${e.lineno}:${e.message}`);e.preventDefault();};// Or, handle uncaught worker errors with a handler outside the worker.worker.onerror=function(e){console.log(`Error in worker at${e.filename}:${e.lineno}:${e.message}`);e.preventDefault();};
letchannel=newMessageChannel;// Create a new channel.letmyPort=channel.port1;// It has two portsletyourPort=channel.port2;// connected to each other.myPort.postMessage("Can you hear me?");// A message posted to one willyourPort.onmessage=(e)=>console.log(e.data);// be received on the other.
letworker=newWorker("worker.js");leturgentChannel=newMessageChannel();leturgentPort=urgentChannel.port1;worker.postMessage({command:"setUrgentPort",value:urgentChannel.port2},[urgentChannel.port2]);// Now we can receive urgent messages from the worker like thisurgentPort.addEventListener("message",handleUrgentMessage);urgentPort.start();// Start receiving messages// And send urgent messages like thisurgentPort.postMessage("test");
The worker creates an ImageData object to represent the rectangular grid of pixels for which it is computing Mandelbrot set membership. But instead of storing actual pixel values in the ImageData, it uses a custom-typed array to treat each pixel as a 32-bit integer. It stores the number of iterations required for each pixel in this array. If the magnitude of the complex number computed for each pixel becomes greater than four, then it is mathematically guaranteed to grow without bounds from then on, and we say it has “escaped.” So the value this worker returns for each pixel is the number of iterations before the value escaped. We tell the worker the maximum number of iterations it should try for each value, and pixels that reach this maximum number are considered to be in the set.
The worker transfers the ArrayBuffer associated with the ImageData back to the main thread so the memory associated with it does not need to be copied.
// This is a simple worker that receives a message from its parent thread,// performs the computation described by that message and then posts the// result of that computation back to the parent thread.onmessage=function(message){// First, we unpack the message we received:// - tile is an object with width and height properties. It specifies the// size of the rectangle of pixels for which we will be computing// Mandelbrot set membership.// - (x0, y0) is the point in the complex plane that corresponds to the// upper-left pixel in the tile.// - perPixel is the pixel size in both the real and imaginary dimensions.// - maxIterations specifies the maximum number of iterations we will// perform before deciding that a pixel is in the set.const{tile,x0,y0,perPixel,maxIterations}=message.data;const{width,height}=tile;// Next, we create an ImageData object to represent the rectangular array// of pixels, get its internal ArrayBuffer, and create a typed array view// of that buffer so we can treat each pixel as a single integer instead of// four individual bytes. We'll store the number of iterations for each// pixel in this iterations array. (The iterations will be transformed into// actual pixel colors in the parent thread.)constimageData=newImageData(width,height);constiterations=newUint32Array(imageData.data.buffer);// Now we begin the computation. There are three nested for loops here.// The outer two loop over the rows and columns of pixels, and the inner// loop iterates each pixel to see if it "escapes" or not. The various// loop variables are the following:// - row and column are integers representing the pixel coordinate.// - x and y represent the complex point for each pixel: x + yi.// - index is the index in the iterations array for the current pixel.// - n tracks the number of iterations for each pixel.// - max and min track the largest and smallest number of iterations// we've seen so far for any pixel in the rectangle.letindex=0,max=0,min=maxIterations;for(letrow=0,y=y0;row<height;row++,y+=perPixel){for(letcolumn=0,x=x0;column<width;column++,x+=perPixel){// For each pixel we start with the complex number c = x+yi.// Then we repeatedly compute the complex number z(n+1) based on// this recursive formula:// z(0) = c// z(n+1) = z(n)^2 + c// If |z(n)| (the magnitude of z(n)) is > 2, then the// pixel is not part of the set and we stop after n iterations.letn;// The number of iterations so farletr=x,i=y;// Start with z(0) set to cfor(n=0;n<maxIterations;n++){letrr=r*r,ii=i*i;// Square the two parts of z(n).if(rr+ii>4){// If |z(n)|^2 is > 4 thenbreak;// we've escaped and can stop iterating.}i=2*r*i+y;// Compute imaginary part of z(n+1).r=rr-ii+x;// And the real part of z(n+1).}iterations[index++]=n;// Remember # iterations for each pixel.if(n>max)max=n;// Track the maximum number we've seen.if(n<min)min=n;// And the minimum as well.}}// When the computation is complete, send the results back to the parent// thread. The imageData object will be copied, but the giant ArrayBuffer// it contains will be transferred for a nice performance boost.postMessage({tile,imageData,min,max},[imageData.data.buffer]);};
/** This class represents a subrectangle of a canvas or image. We use Tiles to* divide a canvas into regions that can be processed independently by Workers.*/classTile{constructor(x,y,width,height){this.x=x;// The properties of a Tile objectthis.y=y;// represent the position and sizethis.width=width;// of the tile within a largerthis.height=height;// rectangle.}// This static method is a generator that divides a rectangle of the// specified width and height into the specified number of rows and// columns and yields numRows*numCols Tile objects to cover the rectangle.static*tiles(width,height,numRows,numCols){letcolumnWidth=Math.ceil(width/numCols);letrowHeight=Math.ceil(height/numRows);for(letrow=0;row<numRows;row++){lettileHeight=(row<numRows-1)?rowHeight// height of most rows:height-rowHeight*(numRows-1);// height of last rowfor(letcol=0;col<numCols;col++){lettileWidth=(col<numCols-1)?columnWidth// width of most columns:width-columnWidth*(numCols-1);// and last columnyieldnewTile(col*columnWidth,row*rowHeight,tileWidth,tileHeight);}}}}/** This class represents a pool of workers, all running the same code. The* worker code you specify must respond to each message it receives by* performing some kind of computation and then posting a single message with* the result of that computation.** Given a WorkerPool and message that represents work to be performed, simply* call addWork(), with the message as an argument. If there is a Worker* object that is currently idle, the message will be posted to that worker* immediately. If there are no idle Worker objects, the message will be* queued and will be posted to a Worker when one becomes available.** addWork() returns a Promise, which will resolve with the message recieved* from the work, or will reject if the worker throws an unhandled error.*/classWorkerPool{constructor(numWorkers,workerSource){this.idleWorkers=[];// Workers that are not currently workingthis.workQueue=[];// Work not currently being processedthis.workerMap=newMap();// Map workers to resolve and reject funcs// Create the specified number of workers, add message and error// handlers and save them in the idleWorkers array.for(leti=0;i<numWorkers;i++){letworker=newWorker(workerSource);worker.onmessage=message=>{this._workerDone(worker,null,message.data);};worker.onerror=error=>{this._workerDone(worker,error,null);};this.idleWorkers[i]=worker;}}// This internal method is called when a worker finishes working, either// by sending a message or by throwing an error._workerDone(worker,error,response){// Look up the resolve() and reject() functions for this worker// and then remove the worker's entry from the map.let[resolver,rejector]=this.workerMap.get(worker);this.workerMap.delete(worker);// If there is no queued work, put this worker back in// the list of idle workers. Otherwise, take work from the queue// and send it to this worker.if(this.workQueue.length===0){this.idleWorkers.push(worker);}else{let[work,resolver,rejector]=this.workQueue.shift();this.workerMap.set(worker,[resolver,rejector]);worker.postMessage(work);}// Finally, resolve or reject the promise associated with the worker.error===null?resolver(response):rejector(error);}// This method adds work to the worker pool and returns a Promise that// will resolve with a worker's response when the work is done. The work// is a value to be passed to a worker with postMessage(). If there is an// idle worker, the work message will be sent immediately. Otherwise it// will be queued until a worker is available.addWork(work){returnnewPromise((resolve,reject)=>{if(this.idleWorkers.length>0){letworker=this.idleWorkers.pop();this.workerMap.set(worker,[resolve,reject]);worker.postMessage(work);}else{this.workQueue.push([work,resolve,reject]);}});}}/** This class holds the state information necessary to render a Mandelbrot set.* The cx and cy properties give the point in the complex plane that is the* center of the image. The perPixel property specifies how much the real and* imaginary parts of that complex number changes for each pixel of the image.* The maxIterations property specifies how hard we work to compute the set.* Larger numbers require more computation but produce crisper images.* Note that the size of the canvas is not part of the state. Given cx, cy, and* perPixel we simply render whatever portion of the Mandelbrot set fits in* the canvas at its current size.** Objects of this type are used with history.pushState() and are used to read* the desired state from a bookmarked or shared URL.*/classPageState{// This factory method returns an initial state to display the entire set.staticinitialState(){lets=newPageState();s.cx=-0.5;s.cy=0;s.perPixel=3/window.innerHeight;s.maxIterations=500;returns;}// This factory method obtains state from a URL, or returns null if// a valid state could not be read from the URL.staticfromURL(url){lets=newPageState();letu=newURL(url);// Initialize state from the url's search params.s.cx=parseFloat(u.searchParams.get("cx"));s.cy=parseFloat(u.searchParams.get("cy"));s.perPixel=parseFloat(u.searchParams.get("pp"));s.maxIterations=parseInt(u.searchParams.get("it"));// If we got valid values, return the PageState object, otherwise null.return(isNaN(s.cx)||isNaN(s.cy)||isNaN(s.perPixel)||isNaN(s.maxIterations))?null:s;}// This instance method encodes the current state into the search// parameters of the browser's current location.toURL(){letu=newURL(window.location);u.searchParams.set("cx",this.cx);u.searchParams.set("cy",this.cy);u.searchParams.set("pp",this.perPixel);u.searchParams.set("it",this.maxIterations);returnu.href;}}// These constants control the parallelism of the Mandelbrot set computation.// You may need to adjust them to get optimum performance on your computer.constROWS=3,COLS=4,NUMWORKERS=navigator.hardwareConcurrency||2;// This is the main class of our Mandelbrot set program. Simply invoke the// constructor function with the <canvas> element to render into. The program// assumes that this <canvas> element is styled so that it is always as big// as the browser window.classMandelbrotCanvas{constructor(canvas){// Store the canvas, get its context object, and initialize a WorkerPoolthis.canvas=canvas;this.context=canvas.getContext("2d");this.workerPool=newWorkerPool(NUMWORKERS,"mandelbrotWorker.js");// Define some properties that we'll use laterthis.tiles=null;// Subregions of the canvasthis.pendingRender=null;// We're not currently renderingthis.wantsRerender=false;// No render is currently requestedthis.resizeTimer=null;// Prevents us from resizing too frequentlythis.colorTable=null;// For converting raw data to pixel values.// Set up our event handlersthis.canvas.addEventListener("pointerdown",e=>this.handlePointer(e));window.addEventListener("keydown",e=>this.handleKey(e));window.addEventListener("resize",e=>this.handleResize(e));window.addEventListener("popstate",e=>this.setState(e.state,false));// Initialize our state from the URL or start with the initial state.this.state=PageState.fromURL(window.location)||PageState.initialState();// Save this state with the history mechanism.history.replaceState(this.state,"",this.state.toURL());// Set the canvas size and get an array of tiles that cover it.this.setSize();// And render the Mandelbrot set into the canvas.this.render();}// Set the canvas size and initialize an array of Tile objects. This// method is called from the constructor and also by the handleResize()// method when the browser window is resized.setSize(){this.width=this.canvas.width=window.innerWidth;this.height=this.canvas.height=window.innerHeight;this.tiles=[...Tile.tiles(this.width,this.height,ROWS,COLS)];}// This function makes a change to the PageState, then re-renders the// Mandelbrot set using that new state, and also saves the new state with// history.pushState(). If the first argument is a function that function// will be called with the state object as its argument and should make// changes to the state. If the first argument is an object, then we simply// copy the properties of that object into the state object. If the optional// second argument is false, then the new state will not be saved. (We// do this when calling setState in response to a popstate event.)setState(f,save=true){// If the argument is a function, call it to update the state.// Otherwise, copy its properties into the current state.if(typeoff==="function"){f(this.state);}else{for(letpropertyinf){this.state[property]=f[property];}}// In either case, start rendering the new state ASAP.this.render();// Normally we save the new state. Except when we're called with// a second argument of false which we do when we get a popstate event.if(save){history.pushState(this.state,"",this.state.toURL());}}// This method asynchronously draws the portion of the Mandelbrot set// specified by the PageState object into the canvas. It is called by// the constructor, by setState() when the state changes, and by the// resize event handler when the size of the canvas changes.render(){// Sometimes the user may use the keyboard or mouse to request renders// more quickly than we can perform them. We don't want to submit all// the renders to the worker pool. Instead if we're rendering, we'll// just make a note that a new render is needed, and when the current// render completes, we'll render the current state, possibly skipping// multiple intermediate states.if(this.pendingRender){// If we're already rendering,this.wantsRerender=true;// make a note to rerender laterreturn;// and don't do anything more now.}// Get our state variables and compute the complex number for the// upper left corner of the canvas.let{cx,cy,perPixel,maxIterations}=this.state;letx0=cx-perPixel*this.width/2;lety0=cy-perPixel*this.height/2;// For each of our ROWS*COLS tiles, call addWork() with a message// for the code in mandelbrotWorker.js. Collect the resulting Promise// objects into an array.letpromises=this.tiles.map(tile=>this.workerPool.addWork({tile:tile,x0:x0+tile.x*perPixel,y0:y0+tile.y*perPixel,perPixel:perPixel,maxIterations:maxIterations}));// Use Promise.all() to get an array of responses from the array of// promises. Each response is the computation for one of our tiles.// Recall from mandelbrotWorker.js that each response includes the// Tile object, an ImageData object that includes iteration counts// instead of pixel values, and the minimum and maximum iterations// for that tile.this.pendingRender=Promise.all(promises).then(responses=>{// First, find the overall max and min iterations over all tiles.// We need these numbers so we can assign colors to the pixels.letmin=maxIterations,max=0;for(letrofresponses){if(r.min<min)min=r.min;if(r.max>max)max=r.max;}// Now we need a way to convert the raw iteration counts from the// workers into pixel colors that will be displayed in the canvas.// We know that all the pixels have between min and max iterations// so we precompute the colors for each iteration count and store// them in the colorTable array.// If we haven't allocated a color table yet, or if it is no longer// the right size, then allocate a new one.if(!this.colorTable||this.colorTable.length!==maxIterations+1){this.colorTable=newUint32Array(maxIterations+1);}// Given the max and the min, compute appropriate values in the// color table. Pixels in the set will be colored fully opaque// black. Pixels outside the set will be translucent black with higher// iteration counts resulting in higher opacity. Pixels with// minimum iteration counts will be transparent and the white// background will show through, resulting in a grayscale image.if(min===max){// If all the pixels are the same,if(min===maxIterations){// Then make them all blackthis.colorTable[min]=0xFF000000;}else{// Or all transparent.this.colorTable[min]=0;}}else{// In the normal case where min and max are different, use a// logarithic scale to assign each possible iteration count an// opacity between 0 and 255, and then use the shift left// operator to turn that into a pixel value.letmaxlog=Math.log(1+max-min);for(leti=min;i<=max;i++){this.colorTable[i]=(Math.ceil(Math.log(1+i-min)/maxlog*255)<<24);}}// Now translate the iteration numbers in each response's// ImageData to colors from the colorTable.for(letrofresponses){letiterations=newUint32Array(r.imageData.data.buffer);for(leti=0;i<iterations.length;i++){iterations[i]=this.colorTable[iterations[i]];}}// Finally, render all the imageData objects into their// corresponding tiles of the canvas using putImageData().// (First, though, remove any CSS transforms on the canvas that may// have been set by the pointerdown event handler.)this.canvas.style.transform="";for(letrofresponses){this.context.putImageData(r.imageData,r.tile.x,r.tile.y);}}).catch((reason)=>{// If anything went wrong in any of our Promises, we'll log// an error here. This shouldn't happen, but this will help with// debugging if it does.console.error("Promise rejected in render():",reason);}).finally(()=>{// When we are done rendering, clear the pendingRender flagsthis.pendingRender=null;// And if render requests came in while we were busy, rerender now.if(this.wantsRerender){this.wantsRerender=false;this.render();}});}// If the user resizes the window, this function will be called repeatedly.// Resizing a canvas and rerendering the Mandlebrot set is an expensive// operation that we can't do multiple times a second, so we use a timer// to defer handling the resize until 200ms have elapsed since the last// resize event was received.handleResize(event){// If we were already deferring a resize, clear it.if(this.resizeTimer)clearTimeout(this.resizeTimer);// And defer this resize instead.this.resizeTimer=setTimeout(()=>{this.resizeTimer=null;// Note that resize has been handledthis.setSize();// Resize canvas and tilesthis.render();// Rerender at the new size},200);}// If the user presses a key, this event handler will be called.// We call setState() in response to various keys, and setState() renders// the new state, updates the URL, and saves the state in browser history.handleKey(event){switch(event.key){case"Escape":// Type Escape to go back to the initial statethis.setState(PageState.initialState());break;case"+":// Type + to increase the number of iterationsthis.setState(s=>{s.maxIterations=Math.round(s.maxIterations*1.5);});break;case"-":// Type - to decrease the number of iterationsthis.setState(s=>{s.maxIterations=Math.round(s.maxIterations/1.5);if(s.maxIterations<1)s.maxIterations=1;});break;case"o":// Type o to zoom outthis.setState(s=>s.perPixel*=2);break;case"ArrowUp":// Up arrow to scroll upthis.setState(s=>s.cy-=this.height/10*s.perPixel);break;case"ArrowDown":// Down arrow to scroll downthis.setState(s=>s.cy+=this.height/10*s.perPixel);break;case"ArrowLeft":// Left arrow to scroll leftthis.setState(s=>s.cx-=this.width/10*s.perPixel);break;case"ArrowRight":// Right arrow to scroll rightthis.setState(s=>s.cx+=this.width/10*s.perPixel);break;}}// This method is called when we get a pointerdown event on the canvas.// The pointerdown event might be the start of a zoom gesture (a click or// tap) or a pan gesture (a drag). This handler registers handlers for// the pointermove and pointerup events in order to respond to the rest// of the gesture. (These two extra handlers are removed when the gesture// ends with a pointerup.)handlePointer(event){// The pixel coordinates and time of the initial pointer down.// Because the canvas is as big as the window, these event coordinates// are also canvas coordinates.constx0=event.clientX,y0=event.clientY,t0=Date.now();// This is the handler for move events.constpointerMoveHandler=event=>{// How much have we moved, and how much time has passed?letdx=event.clientX-x0,dy=event.clientY-y0,dt=Date.now()-t0;// If the pointer has moved enough or enough time has passed that// this is not a regular click, then use CSS to pan the display.// (We will rerender it for real when we get the pointerup event.)if(dx>10||dy>10||dt>500){this.canvas.style.transform=`translate(${dx}px,${dy}px)`;}};// This is the handler for pointerup eventsconstpointerUpHandler=event=>{// When the pointer goes up, the gesture is over, so remove// the move and up handlers until the next gesture.this.canvas.removeEventListener("pointermove",pointerMoveHandler);this.canvas.removeEventListener("pointerup",pointerUpHandler);// How much did the pointer move, and how much time passed?constdx=event.clientX-x0,dy=event.clientY-y0,dt=Date.now()-t0;// Unpack the state object into individual constants.const{cx,cy,perPixel}=this.state;// If the pointer moved far enough or if enough time passed, then// this was a pan gesture, and we need to change state to change// the center point. Otherwise, the user clicked or tapped on a// point and we need to center and zoom in on that point.if(dx>10||dy>10||dt>500){// The user panned the image by (dx, dy) pixels.// Convert those values to offsets in the complex plane.this.setState({cx:cx-dx*perPixel,cy:cy-dy*perPixel});}else{// The user clicked. Compute how many pixels the center moves.letcdx=x0-this.width/2;letcdy=y0-this.height/2;// Use CSS to quickly and temporarily zoom inthis.canvas.style.transform=`translate(${-cdx*2}px,${-cdy*2}px) scale(2)`;// Set the complex coordinates of the new center point and// zoom in by a factor of 2.this.setState(s=>{s.cx+=cdx*s.perPixel;s.cy+=cdy*s.perPixel;s.perPixel/=2;});}};// When the user begins a gesture we register handlers for the// pointermove and pointerup events that follow.this.canvas.addEventListener("pointermove",pointerMoveHandler);this.canvas.addEventListener("pointerup",pointerUpHandler);}}// Finally, here's how we set up the canvas. Note that this JavaScript file// is self-sufficient. The HTML file only needs to include this one <script>.letcanvas=document.createElement("canvas");// Create a canvas elementdocument.body.append(canvas);// Insert it into the bodydocument.body.style="margin:0";// No margin for the <body>canvas.style.width="100%";// Make canvas as wide as bodycanvas.style.height="100%";// and as high as the body.newMandelbrotCanvas(canvas);// And start rendering into it!
How scripts and JavaScript modules are included in web pages and how and when they are executed.
Client-side JavaScript’s asynchronous, event-driven programming model.
The Document Object Model (DOM) that allows JavaScript code to inspect and modify the HTML content of the document it is embedded within. This DOM API is the heart of all client-side JavaScript programming.
How JavaScript code can manipulate the CSS styles that are applied to content within the document.
How JavaScript code can obtain the coordinates of document elements in the browser window and within the document itself.
How to create reusable UI “Web Components” with JavaScript, HTML, and CSS using the Custom Elements and Shadow DOM APIs.
How to display and dynamically generate graphics with SVG and the
HTML <canvas> element.
How to add scripted sound effects (both recorded and synthesized) to your web pages.
How JavaScript can make the browser load new pages, go backward and forward in the user’s browsing history, and even add new entries to the browsing history.
How JavaScript programs can exchange data with web servers using the HTTP and WebSocket protocols.
How JavaScript programs can store data in the user’s browser.
How JavaScript programs can use worker threads to achieve a safe form of concurrency.
The Window object defines alert(), confirm(), and prompt()
methods that display simple modal dialogues to the user. These methods
block the main thread. The confirm() method synchronously returns a
boolean value, and prompt() synchronously returns a string of user
input. These are not suitable for production use but can be useful
for simple projects and prototypes.
The navigator and screen properties of the Window object were
mentioned in passing at the start of this chapter, but the Navigator
and Screen objects that they reference have some features that were
not described here that you may find useful.
The requestFullscreen() method of any Element object requests that
that element (a <video> or <canvas> element, for example) be
displayed in fullscreen mode. The exitFullscreen() method of the
Document returns to normal display mode.
The requestAnimationFrame() method of the Window object takes a
function as its argument and will execute that function when the
browser is preparing to render the next frame. When you are making
visual changes (especially repeated or animated ones), wrapping your
code within a call to requestAnimationFrame() can help to ensure
that the changes are rendered smoothly and in a way that is optimized
by the browser.
If the user selects text within your document, you can obtain details
of that selection with the Window method getSelection() and get the
selected text with getSelection().toString(). In some browsers,
navigator.clipboard is an object with an async API for reading and
setting the content of the system
clipboard to enable copy-and-paste
interactions with applications outside of the browser.
A little-known feature of web browsers is that HTML elements with a
contenteditable="true" attribute allow their content to be
edited. The document.execCommand() method enables rich-text editing
features for editable content.
A MutationObserver allows JavaScript to monitor changes to, or
beneath, a specified element in the document. Create a
MutationObserver with the MutationObserver() constructor, passing
the callback function that should be called when changes are
made. Then call the observe() method of the MutationObserver to
specify which parts of which element are to be monitored.
An IntersectionObserver allows JavaScript to determine which document elements are on the screen and which are close to being on the screen. It is particularly useful for applications that want to dynamically load content on demand as the user scrolls.
Browsers fire “online” and “offline” events at the Window object when the browser gains or loses an internet connection.
Browsers fire a “visiblitychange” event at the Document object when
a document becomes visible or invisible (usually because a user has
switched tabs). JavaScript can check document.visibilityState to
determine whether its document is currently “visible” or “hidden.”
Browsers support a complicated API to support drag-and-drop UIs and to support data exchange with applications outside the browser. This API involves a number of events, including “dragstart,” “dragover,” “dragend,” and “drop.” This API is tricky to use correctly but useful when you need it. It is an important API to know about if you want to enable users to drag files from their desktop into your web application.
The Pointer Lock API enables JavaScript to hide the mouse pointer and
get raw mouse events as relative movement amounts rather than absolute
positions on the screen. This is typically useful for games. Call
requestPointerLock() on the element you want all mouse events
directed to. After you do this, “mousemove” events delivered to that
element will have movementX and movementY properties.
The Gamepad API adds support for game controllers. Use
navigator.getGamepads() to get connected Gamepad objects, and listen
for “gamepadconnected” events on the Window object to be notified when
a new controller is plugged in. The Gamepad object defines an API for
querying the current state of the buttons on the controller.
A ServiceWorker is a kind of worker thread with the ability to intercept, inspect, and respond to network requests from the web application that it “services.” When a web application registers a service worker, that worker’s code becomes persistent in the browser’s local storage, and when the user visits the associated website again, the service worker is reactivated. Service workers can cache network responses (including files of JavaScript code), which means that web applications that use service workers can effectively install themselves onto the user’s computer for rapid startup and offline use. The Service Worker Cookbook at https://serviceworke.rs is a valuable resource for learning about service workers and their related technologies.
The Cache API is designed for use by service workers (but is also
available to regular JavaScript code outside of workers). It works
with the Request and Response objects defined by the fetch() API and
implements a cache of Request/Response pairs. The Cache API enables a
service worker to cache the scripts and other assets of the web app it
serves and can also help to enable offline use of the web app (which
is particularly important for mobile devices).
A Web Manifest is a JSON-formatted file that describes a web
application including a name, a URL, and links to icons in various
sizes. If your web app uses a service worker and includes a <link
rel="manifest"> tag that references a .webmanifest file, then
browsers (particularly browsers on mobile devices) may give you the
option to add an icon for the web app to your desktop or home screen.
The Notifications API allows web apps to display notifications using the native OS notification system on both mobile and desktop devices. Notifications can include an image and text, and your code can receive an event if the user clicks on the notification. Using this API is complicated by the fact that you must first request the user’s permission to display notifications.
The Push API allows web applications that have a service worker (and that have the user’s permission) to subscribe to notifications from a server, and to display those notifications even when the application itself is not running. Push notifications are common on mobile devices, and the Push API brings web apps closer to feature parity with native apps on mobile.
The Geolocation API allows JavaScript (with the user’s permission) to
determine the user’s physical location. It is well supported on
desktop and mobile devices, including iOS devices. Use
navigator.geolocation.getCurrentPosition() to request the user’s current
position and use navigator.geolocation.watchPosition() to register a
callback to be called when the user’s position changes.
The navigator.vibrate() method causes a mobile device (but not iOS)
to vibrate. Often this is only allowed in response to a user gesture,
but calling this method will allow your app to provide silent feedback
that a gesture has been
recognized.
The ScreenOrientation API enables a web application to query the current orientation of a mobile device screen and also to lock themselves to landscape or portrait orientation.
The “devicemotion” and “deviceorientation” events on the window object report accelerometer and magnetometer data for the device, enabling you to determine how the device is accelerating and how the user is orienting it in space. (These events do work on iOS.)
The Sensor API is not yet widely supported beyond Chrome on Android devices, but it enables JavaScript access to the full suite of mobile device sensors, including accelerometer, gyroscope, magnetometer, and ambient light sensor. These sensors enable JavaScript to determine which direction a user is facing or to detect when the user shakes their phone, for example.
1 Previous editions of this book had an extensive reference section covering the JavaScript standard library and web APIs. It was removed in the seventh edition because MDN has made it obsolete: today, it is quicker to look something up on MDN than it is to flip through a book, and my former colleagues at MDN do a better job at keeping their online documentation up to date than this book ever could.
2 Some sources, including the HTML specification, make a technical distinction between handlers and listeners, based on the way in which they are registered. In this book, we treat the two terms as synonyms.
3 If you have used the React framework to create client-side user interfaces, this may surprise you. React makes a number of minor changes to the client-side event model, and one of them is that in React, event handler property names are written in camelCase: onClick, onMouseOver, and so on. When working with the web platform natively, however, the event handler properties are written entirely in lowercase.
4 The custom element specification allows subclassing of <button> and other specific element classes, but this is not supported in Safari and a different syntax is required to use a custom element that extends anything other than HTMLElement.
Modern alternative to shell scripts that does not suffer from the arcane syntax of bash and other Unix shells.
General-purpose programming language for running trusted programs, not subject to the security constraints imposed by web browsers on untrusted code.
Popular environment for writing efficient and highly concurrent web servers.
console.log("Hello World!");
console.log(process.argv);
$ node --trace-uncaught argv.js --arg1 --arg2 filename [ '/usr/local/bin/node', '/private/tmp/argv.js', '--arg1', '--arg2', 'filename' ]
The first and second elements of process.argv will be
fully qualified filesystem paths to the Node executable and the file
of JavaScript that is being executed, even if you did not type them
that way.
Command-line arguments that are intended for and interpreted by the
Node executable itself are consumed by the Node executable and do
not appear in process.argv. (The --trace-uncaught command-line
argument isn’t actually doing anything useful in the previous example;
it is just there to demonstrate that it does not appear in the
output.) Any arguments (such as --arg1 and filename) that
appear after the name of the JavaScript file will appear in
process.argv.
$ node -p -e 'process.env'
{
SHELL: '/bin/bash',
USER: 'david',
PATH: '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin',
PWD: '/tmp',
LANG: 'en_US.UTF-8',
HOME: '/Users/david',
}
process.setUncaughtExceptionCaptureCallback(e=>{console.error("Uncaught exception:",e);});
process.on("unhandledRejection",(reason,promise)=>{// reason is whatever value would have been passed to a .catch() function// promise is the Promise object that rejected});
$ npm install express npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN my-server@1.0.0 No description npm WARN my-server@1.0.0 No repository field. + express@4.17.1 added 50 packages from 37 contributors and audited 126 packages in 3.058s found 0 vulnerabilities
constfs=require("fs");// Require the filesystem module// Read a config file, parse its contents as JSON, and pass the// resulting value to the callback. If anything goes wrong,// print an error message to stderr and invoke the callback with nullfunctionreadConfigFile(path,callback){fs.readFile(path,"utf8",(err,text)=>{if(err){// Something went wrong reading the fileconsole.error(err);callback(null);return;}letdata=null;try{data=JSON.parse(text);}catch(e){// Something went wrong parsing the file contentsconsole.error(e);}callback(data);});}
constutil=require("util");constfs=require("fs");// Require the filesystem moduleconstpfs={// Promise-based variants of some fs functionsreadFile:util.promisify(fs.readFile)};functionreadConfigFile(path){returnpfs.readFile(path,"utf-8").then(text=>{returnJSON.parse(text);});}
asyncfunctionreadConfigFile(path){lettext=awaitpfs.readFile(path,"utf-8");returnJSON.parse(text);}
constfs=require("fs");functionreadConfigFileSync(path){lettext=fs.readFileSync(path,"utf-8");returnJSON.parse(text);}
"utf8"This is the default when no encoding is specified, and is the Unicode encoding you are most likely to use.
"utf16le"Two-byte Unicode characters, with little-endian
ordering. Codepoints above \uffff are encoded as a pair of
two-byte sequences. Encoding "ucs2" is an alias.
"latin1"The one-byte-per-character ISO-8859-1 encoding that
defines a character set suitable for many Western European
languages. Because there is a one-to-one mapping between bytes and
latin-1 characters, this encoding is also known as "binary".
"ascii"The 7-bit English-only ASCII encoding, a strict subset of
the "utf8" encoding.
"hex"This encoding converts each byte to a pair of ASCII hexadecimal digits.
"base64"This encoding converts each sequence of three bytes into a sequence of four ascii characters.
letb=Buffer.from([0x41,0x42,0x43]);// <Buffer 41 42 43>b.toString()// => "ABC"; default "utf8"b.toString("hex")// => "414243"letcomputer=Buffer.from("IBM3111","ascii");// Convert string to Bufferfor(leti=0;i<computer.length;i++){// Use Buffer as byte arraycomputer[i]--;// Buffers are mutable}computer.toString("ascii")// => "HAL2000"computer.subarray(0,3).map(x=>x+1).toString()// => "IBM"// Create new "empty" buffers with Buffer.alloc()letzeros=Buffer.alloc(1024);// 1024 zerosletones=Buffer.alloc(128,1);// 128 onesletdead=Buffer.alloc(1024,"DEADBEEF","hex");// Repeating pattern of bytes// Buffers have methods for reading and writing multi-byte values// from and to a buffer at any specified offset.dead.readUInt32BE(0)// => 0xDEADBEEFdead.readUInt32BE(1)// => 0xADBEEFDEdead.readBigUInt64BE(6)// => 0xBEEFDEADBEEFDEADndead.readUInt32LE(1020)// => 0xEFBEADDE
constEventEmitter=require("events");// Module name does not match class nameconstnet=require("net");letserver=newnet.Server();// create a Server objectserverinstanceofEventEmitter// => true: Servers are EventEmitters
constnet=require("net");letserver=newnet.Server();// create a Server objectserver.on("connection",socket=>{// Listen for "connection" events// Server "connection" events are passed a socket object// for the client that just connected. Here we send some data// to the client and disconnect.socket.end("Hello World","utf8");});
constfs=require("fs");// An asynchronous but nonstreaming (and therefore inefficient) function.functioncopyFile(sourceFilename,destinationFilename,callback){fs.readFile(sourceFilename,(err,buffer)=>{if(err){callback(err);}else{fs.writeFile(destinationFilename,buffer,callback);}});}
Readable streams are sources of data. The stream returned
by fs.createReadStream(), for example, is a stream from which the
content of a specified file can be read. process.stdin is another
Readable stream that returns data from standard input.
Writable streams are sinks or destinations for data. The
return value of fs.createWriteStream(), for example, is a Writable
stream: it allows data to be written to it in chunks, and outputs all
of that data to a specified file.
Duplex streams combine a Readable stream and a Writable
stream into one object. The Socket objects returned by net.connect()
and other Node networking APIs, for example, are Duplex streams. If
you write to a socket, your data is sent across the network to
whatever computer the socket is connected to. And if you read from a
socket, you access the data written by that other computer.
Transform streams are also readable and writable, but they
differ from Duplex streams in an important way: data written to a
Transform stream becomes readable—usually in some transformed
form—from the same stream. The zlib.createGzip() function, for
example, returns a Transform stream that compresses (with the gzip
algorithm) the data written to it. In a similar way, the
crypto.createCipheriv() function returns a Transform stream that
encrypts or decrypts data that is written to it.
constfs=require("fs");functionpipeFileToSocket(filename,socket){fs.createReadStream(filename).pipe(socket);}
functionpipe(readable,writable,callback){// First, set up error handlingfunctionhandleError(err){readable.close();writable.close();callback(err);}// Next define the pipe and handle the normal termination casereadable.on("error",handleError).pipe(writable).on("error",handleError).on("finish",callback);}
constfs=require("fs");constzlib=require("zlib");functiongzip(filename,callback){// Create the streamsletsource=fs.createReadStream(filename);letdestination=fs.createWriteStream(filename+".gz");letgzipper=zlib.createGzip();// Set up the pipelinesource.on("error",callback)// call callback on read error.pipe(gzipper).pipe(destination).on("error",callback)// call callback on write error.on("finish",callback);// call callback when writing is complete}
conststream=require("stream");classGrepStreamextendsstream.Transform{constructor(pattern){super({decodeStrings:false});// Don't convert strings back to buffersthis.pattern=pattern;// The regular expression we want to matchthis.incompleteLine="";// Any remnant of the last chunk of data}// This method is invoked when there is a string ready to be// transformed. It should pass transformed data to the specified// callback function. We expect string input so this stream should// only be connected to readable streams that have had// setEncoding() called on them._transform(chunk,encoding,callback){if(typeofchunk!=="string"){callback(newError("Expected a string but got a buffer"));return;}// Add the chunk to any previously incomplete line and break// everything into linesletlines=(this.incompleteLine+chunk).split("\n");// The last element of the array is the new incomplete linethis.incompleteLine=lines.pop();// Find all matching linesletoutput=lines// Start with all complete lines,.filter(l=>this.pattern.test(l))// filter them for matches,.join("\n");// and join them back up.// If anything matched, add a final newlineif(output){output+="\n";}// Always call the callback even if there is no outputcallback(null,output);}// This is called right before the stream is closed.// It is our chance to write out any last data._flush(callback){// If we still have an incomplete line, and it matches// pass it to the callbackif(this.pattern.test(this.incompleteLine)){callback(null,this.incompleteLine+"\n");}}}// Now we can write a program like 'grep' with this class.letpattern=newRegExp(process.argv[2]);// Get a RegExp from command line.process.stdin// Start with standard input,.setEncoding("utf8")// read it as Unicode strings,.pipe(newGrepStream(pattern))// pipe it to our GrepStream,.pipe(process.stdout)// and pipe that to standard out..on("error",()=>process.exit());// Exit gracefully if stdout closes.
// Read lines of text from the source stream, and write any lines// that match the specified pattern to the destination stream.asyncfunctiongrep(source,destination,pattern,encoding="utf8"){// Set up the source stream for reading strings, not Bufferssource.setEncoding(encoding);// Set an error handler on the destination stream in case standard// output closes unexpectedly (when piping output to `head`, e.g.)destination.on("error",err=>process.exit());// The chunks we read are unlikely to end with a newline, so each will// probably have a partial line at the end. Track that hereletincompleteLine="";// Use a for/await loop to asynchronously read chunks from the input streamforawait(letchunkofsource){// Split the end of the last chunk plus this one into linesletlines=(incompleteLine+chunk).split("\n");// The last line is incompleteincompleteLine=lines.pop();// Now loop through the lines and write any matches to the destinationfor(letlineoflines){if(pattern.test(line)){destination.write(line+"\n",encoding);}}}// Finally, check for a match on any trailing text.if(pattern.test(incompleteLine)){destination.write(incompleteLine+"\n",encoding);}}letpattern=newRegExp(process.argv[2]);// Get a RegExp from command line.grep(process.stdin,process.stdout,pattern)// Call the async grep() function..catch(err=>{// Handle asynchronous exceptions.console.error(err);process.exit();});
functionwrite(stream,chunk,callback){// Write the specified chunk to the specified streamlethasMoreRoom=stream.write(chunk);// Check the return value of the write() method:if(hasMoreRoom){// If it returned true, thensetImmediate(callback);// invoke callback asynchronously.}else{// If it returned false, thenstream.once("drain",callback);// invoke callback on drain event.}}
// This function writes the specified chunk to the specified stream and// returns a Promise that will be fulfilled when it is OK to write again.// Because it returns a Promise, it can be used with await.functionwrite(stream,chunk){// Write the specified chunk to the specified streamlethasMoreRoom=stream.write(chunk);if(hasMoreRoom){// If buffer is not full, returnreturnPromise.resolve(null);// an already resolved Promise object}else{returnnewPromise(resolve=>{// Otherwise, return a Promise thatstream.once("drain",resolve);// resolves on the drain event.});}}// Copy data from the source stream to the destination stream// respecting backpressure from the destination stream.// This is much like calling source.pipe(destination).asyncfunctioncopy(source,destination){// Set an error handler on the destination stream in case standard// output closes unexpectedly (when piping output to `head`, e.g.)destination.on("error",err=>process.exit());// Use a for/await loop to asynchronously read chunks from the input streamforawait(letchunkofsource){// Write the chunk and wait until there is more room in the buffer.awaitwrite(destination,chunk);}}// Copy standard input to standard outputcopy(process.stdin,process.stdout);
constfs=require("fs");// A streaming file copy function, using "flowing mode".// Copies the contents of the named source file to the named destination file.// On success, invokes the callback with a null argument. On error,// invokes the callback with an Error object.functioncopyFile(sourceFilename,destinationFilename,callback){letinput=fs.createReadStream(sourceFilename);letoutput=fs.createWriteStream(destinationFilename);input.on("data",(chunk)=>{// When we get new data,lethasRoom=output.write(chunk);// write it to the output stream.if(!hasRoom){// If the output stream is fullinput.pause();// then pause the input stream.}});input.on("end",()=>{// When we reach the end of input,output.end();// tell the output stream to end.});input.on("error",err=>{// If we get an error on the input,callback(err);// call the callback with the errorprocess.exit();// and quit.});output.on("drain",()=>{// When the output is no longer full,input.resume();// resume data events on the input});output.on("error",err=>{// If we get an error on the output,callback(err);// call the callback with the errorprocess.exit();// and quit.});output.on("finish",()=>{// When output is fully writtencallback(null);// call the callback with no error.});}// Here's a simple command-line utility to copy filesletfrom=process.argv[2],to=process.argv[3];console.log(`Copying file${from}to${to}...`);copyFile(from,to,err=>{if(err){console.error(err);}else{console.log("done.");}});
constfs=require("fs");constcrypto=require("crypto");// Compute a sha256 hash of the contents of the named file and pass the// hash (as a string) to the specified error-first callback function.functionsha256(filename,callback){letinput=fs.createReadStream(filename);// The data stream.lethasher=crypto.createHash("sha256");// For computing the hash.input.on("readable",()=>{// When there is data ready to readletchunk;while(chunk=input.read()){// Read a chunk, and if non-null,hasher.update(chunk);// pass it to the hasher,}// and keep looping until not readable});input.on("end",()=>{// At the end of the stream,lethash=hasher.digest("hex");// compute the hash,callback(null,hash);// and pass it to the callback.});input.on("error",callback);// On error, call callback}// Here's a simple command-line utility to compute the hash of a filesha256(process.argv[2],(err,hash)=>{// Pass filename from command line.if(err){// If we get an errorconsole.error(err.toString());// print it as an error.}else{// Otherwise,console.log(hash);// print the hash string.}});
process.argv// An array of command-line arguments.process.arch// The CPU architecture: "x64", for example.process.cwd()// Returns the current working directory.process.chdir()// Sets the current working directory.process.cpuUsage()// Reports CPU usage.process.env// An object of environment variables.process.execPath// The absolute filesystem path to the node executable.process.exit()// Terminates the program.process.exitCode// An integer code to be reported when the program exits.process.getuid()// Return the Unix user id of the current user.process.hrtime.bigint()// Return a "high-resolution" nanosecond timestamp.process.kill()// Send a signal to another process.process.memoryUsage()// Return an object with memory usage details.process.nextTick()// Like setImmediate(), invoke a function soon.process.pid// The process id of the current process.process.ppid// The parent process id.process.platform// The OS: "linux", "darwin", or "win32", for example.process.resourceUsage()// Return an object with resource usage details.process.setuid()// Sets the current user, by id or name.process.title// The process name that appears in `ps` listings.process.umask()// Set or return the default permissions for new files.process.uptime()// Return Node's uptime in seconds.process.version// Node's version string.process.versions// Version strings for the libraries Node depends on.
constos=require("os");os.arch()// Returns CPU architecture. "x64" or "arm", for example.os.constants// Useful constants such as os.constants.signals.SIGINT.os.cpus()// Data about system CPU cores, including usage times.os.endianness()// The CPU's native endianness "BE" or "LE".os.EOL// The OS native line terminator: "\n" or "\r\n".os.freemem()// Returns the amount of free RAM in bytes.os.getPriority()// Returns the OS scheduling priority of a process.os.homedir()// Returns the current user's home directory.os.hostname()// Returns the hostname of the computer.os.loadavg()// Returns the 1, 5, and 15-minute load averages.os.networkInterfaces()// Returns details about available network. connections.os.platform()// Returns OS: "linux", "darwin", or "win32", for example.os.release()// Returns the version number of the OS.os.setPriority()// Attempts to set the scheduling priority for a process.os.tmpdir()// Returns the default temporary directory.os.totalmem()// Returns the total amount of RAM in bytes.os.type()// Returns OS: "Linux", "Darwin", or "Windows_NT", e.g.os.uptime()// Returns the system uptime in seconds.os.userInfo()// Returns uid, username, home, and shell of current user.
// Some important pathsprocess.cwd()// Absolute path of the current working directory.__filename// Absolute path of the file that holds the current code.__dirname// Absolute path of the directory that holds __filename.os.homedir()// The user's home directory.constpath=require("path");path.sep// Either "/" or "\" depending on your OS// The path module has simple parsing functionsletp="src/pkg/test.js";// An example pathpath.basename(p)// => "test.js"path.extname(p)// => ".js"path.dirname(p)// => "src/pkg"path.basename(path.dirname(p))// => "pkg"path.dirname(path.dirname(p))// => "src"// normalize() cleans up paths:path.normalize("a/b/c/../d/")// => "a/b/d/": handles ../ segmentspath.normalize("a/./b")// => "a/b": strips "./" segmentspath.normalize("//a//b//")// => "/a/b/": removes duplicate /// join() combines path segments, adding separators, then normalizespath.join("src","pkg","t.js")// => "src/pkg/t.js"// resolve() takes one or more path segments and returns an absolute// path. It starts with the last argument and works backward, stopping// when it has built an absolute path or resolving against process.cwd().path.resolve()// => process.cwd()path.resolve("t.js")// => path.join(process.cwd(), "t.js")path.resolve("/tmp","t.js")// => "/tmp/t.js"path.resolve("/a","/b","t.js")// => "/b/t.js"
constfs=require("fs");letbuffer=fs.readFileSync("test.data");// Synchronous, returns bufferlettext=fs.readFileSync("data.csv","utf8");// Synchronous, returns string// Read the bytes of the file asynchronouslyfs.readFile("test.data",(err,buffer)=>{if(err){// Handle the error here}else{// The bytes of the file are in buffer}});// Promise-based asynchronous readfs.promises.readFile("data.csv","utf8").then(processFileText).catch(handleReadError);// Or use the Promise API with await inside an async functionasyncfunctionprocessText(filename,encoding="utf8"){lettext=awaitfs.promises.readFile(filename,encoding);// ... process the text here...}
functionprintFile(filename,encoding="utf8"){fs.createReadStream(filename,encoding).pipe(process.stdout);}
constfs=require("fs");// Reading a specific portion of a data filefs.open("data",(err,fd)=>{if(err){// Report error somehowreturn;}try{// Read bytes 20 through 420 into a newly allocated buffer.fs.read(fd,Buffer.alloc(400),0,400,20,(err,n,b)=>{// err is the error, if any.// n is the number of bytes actually read// b is the buffer that they bytes were read into.});}finally{// Use a finally clause so we alwaysfs.close(fd);// close the open file descriptor}});
constfs=require("fs");functionreadData(filename){letfd=fs.openSync(filename);try{// Read the file headerletheader=Buffer.alloc(12);// A 12 byte bufferfs.readSync(fd,header,0,12,0);// Verify the file's magic numberletmagic=header.readInt32LE(0);if(magic!==0xDADAFEED){thrownewError("File is of wrong type");}// Now get the offset and length of the data from the headerletoffset=header.readInt32LE(4);letlength=header.readInt32LE(8);// And read those bytes from the fileletdata=Buffer.alloc(length);fs.readSync(fd,data,0,length,offset);returndata;}finally{// Always close the file, even if an exception is thrown abovefs.closeSync(fd);}}
fs.writeFileSync(path.resolve(__dirname,"settings.json"),JSON.stringify(settings));
constfs=require("fs");letoutput=fs.createWriteStream("numbers.txt");for(leti=0;i<100;i++){output.write(`${i}\n`);}output.end();
// Basic synchronous file copy.fs.copyFileSync("ch15.txt","ch15.bak");// The COPYFILE_EXCL argument copies only if the new file does not already// exist. It prevents copies from overwriting existing files.fs.copyFile("ch15.txt","ch16.txt",fs.constants.COPYFILE_EXCL,err=>{// This callback will be called when done. On error, err will be non-null.});// This code demonstrates the Promise-based version of the copyFile function.// Two flags are combined with the bitwise OR opeartor |. The flags mean that// existing files won't be overwritten, and that if the filesystem supports// it, the copy will be a copy-on-write clone of the original file, meaning// that no additional storage space will be required until either the original// or the copy is modified.fs.promises.copyFile("Important data",`Important data${newDate().toISOString()}"fs.constants.COPYFILE_EXCL | fs.constants.COPYFILE_FICLONE).then(() => {console.log("Backup complete");});.catch(err => {console.error("Backup failed", err);});
fs.renameSync("ch15.bak","backups/ch15.bak");
fs.unlinkSync("backups/ch15.bak");
constfs=require("fs");letstats=fs.statSync("book/ch15.md");stats.isFile()// => true: this is an ordinary filestats.isDirectory()// => false: it is not a directorystats.size// file size in bytesstats.atime// access time: Date when it was last readstats.mtime// modification time: Date when it was last writtenstats.uid// the user id of the file's ownerstats.gid// the group id of the file's ownerstats.mode.toString(8)// the file's permissions, as an octal string
fs.chmodSync("ch15.md",0o400);// Don't delete it accidentally!
// Ensure that dist/ and dist/lib/ both exist.fs.mkdirSync("dist/lib",{recursive:true});
// Create a random temporary directory and get its path, then// delete it when we are donelettempDirPath;try{tempDirPath=fs.mkdtempSync(path.join(os.tmpdir(),"d"));// Do something with the directory here}finally{// Delete the temporary directory when we're done with itfs.rmdirSync(tempDirPath);}
lettempFiles=fs.readdirSync("/tmp");// returns an array of strings// Use the Promise-based API to get a Dirent array, and then// print the paths of subdirectoriesfs.promises.readdir("/tmp",{withFileTypes:true}).then(entries=>{entries.filter(entry=>entry.isDirectory()).map(entry=>entry.name).forEach(name=>console.log(path.join("/tmp/",name)));}).catch(console.error);
constfs=require("fs");constpath=require("path");asyncfunctionlistDirectory(dirpath){letdir=awaitfs.promises.opendir(dirpath);forawait(letentryofdir){letname=entry.name;if(entry.isDirectory()){name+="/";// Add a trailing slash to subdirectories}letstats=awaitfs.promises.stat(path.join(dirpath,name));letsize=stats.size;console.log(String(size).padStart(10),name);}}
consthttps=require("https");/** Convert the body object to a JSON string then HTTPS POST it to the* specified API endpoint on the specified host. When the response arrives,* parse the response body as JSON and resolve the returned Promise with* that parsed value.*/functionpostJSON(host,endpoint,body,port,username,password){// Return a Promise object immediately, then call resolve or reject// when the HTTPS request succeeds or fails.returnnewPromise((resolve,reject)=>{// Convert the body object to a stringletbodyText=JSON.stringify(body);// Configure the HTTPS requestletrequestOptions={method:"POST",// Or "GET", "PUT", "DELETE", etc.host:host,// The host to connect topath:endpoint,// The URL pathheaders:{// HTTP headers for the request"Content-Type":"application/json","Content-Length":Buffer.byteLength(bodyText)}};if(port){// If a port is specified,requestOptions.port=port;// use it for the request.}// If credentials are specified, add an Authorization header.if(username&&password){requestOptions.auth=`${username}:${password}`;}// Now create the request based on the configuration objectletrequest=https.request(requestOptions);// Write the body of the POST request and end the request.request.write(bodyText);request.end();// Fail on request errors (such as no network connection)request.on("error",e=>reject(e));// Handle the response when it starts to arrive.request.on("response",response=>{if(response.statusCode!==200){reject(newError(`HTTP status${response.statusCode}`));// We don't care about the response body in this case, but// we don't want it to stick around in a buffer somewhere, so// we put the stream into flowing mode without registering// a "data" handler so that the body is discarded.response.resume();return;}// We want text, not bytes. We're assuming the text will be// JSON-formatted but aren't bothering to check the// Content-Type header.response.setEncoding("utf8");// Node doesn't have a streaming JSON parser, so we read the// entire response body into a string.letbody="";response.on("data",chunk=>{body+=chunk;});// And now handle the response when it is complete.response.on("end",()=>{// When the response is done,try{// try to parse it as JSONresolve(JSON.parse(body));// and resolve the result.}catch(e){// Or, if anything goes wrong,reject(e);// reject with the error}});});});}
Create a new Server object.
Call its listen() method to begin listening for requests on a
specified port.
Register an event handler for “request” events, use that handler to
read the client’s request (particularly the request.url property),
and write your response.
// This is a simple static HTTP server that serves files from a specified// directory. It also implements a special /test/mirror endpoint that// echoes the incoming request, which can be useful when debugging clients.consthttp=require("http");// Use "https" if you have a certificateconsturl=require("url");// For parsing URLsconstpath=require("path");// For manipulating filesystem pathsconstfs=require("fs");// For reading files// Serve files from the specified root directory via an HTTP server that// listens on the specified port.functionserve(rootDirectory,port){letserver=newhttp.Server();// Create a new HTTP serverserver.listen(port);// Listen on the specified portconsole.log("Listening on port",port);// When requests come in, handle them with this functionserver.on("request",(request,response)=>{// Get the path portion of the request URL, ignoring// any query parameters that are appended to it.letendpoint=url.parse(request.url).pathname;// If the request was for "/test/mirror", send back the request// verbatim. Useful when you need to see the request headers and body.if(endpoint==="/test/mirror"){// Set response headerresponse.setHeader("Content-Type","text/plain; charset=UTF-8");// Specify response status coderesponse.writeHead(200);// 200 OK// Begin the response body with the requestresponse.write(`${request.method}${request.url}HTTP/${request.httpVersion}\r\n`);// Output the request headersletheaders=request.rawHeaders;for(leti=0;i<headers.length;i+=2){response.write(`${headers[i]}:${headers[i+1]}\r\n`);}// End headers with an extra blank lineresponse.write("\r\n");// Now we need to copy any request body to the response body// Since they are both streams, we can use a piperequest.pipe(response);}// Otherwise, serve a file from the local directory.else{// Map the endpoint to a file in the local filesystemletfilename=endpoint.substring(1);// strip leading /// Don't allow "../" in the path because it would be a security// hole to serve anything outside the root directory.filename=filename.replace(/\.\.\//g,"");// Now convert from relative to absolute filenamefilename=path.resolve(rootDirectory,filename);// Now guess the type file's content type based on extensionlettype;switch(path.extname(filename)){case".html":case".htm":type="text/html";break;case".js":type="text/javascript";break;case".css":type="text/css";break;case".png":type="image/png";break;case".txt":type="text/plain";break;default:type="application/octet-stream";break;}letstream=fs.createReadStream(filename);stream.once("readable",()=>{// If the stream becomes readable, then set the// Content-Type header and a 200 OK status. Then pipe the// file reader stream to the response. The pipe will// automatically call response.end() when the stream ends.response.setHeader("Content-Type",type);response.writeHead(200);stream.pipe(response);});stream.on("error",(err)=>{// Instead, if we get an error trying to open the stream// then the file probably does not exist or is not readable.// Send a 404 Not Found plain-text response with the// error message.response.setHeader("Content-Type","text/plain; charset=UTF-8");response.writeHead(404);response.end(err.message);});}});}// When we're invoked from the command line, call the serve() functionserve(process.argv[2]||"/tmp",parseInt(process.argv[3])||8000);
// A TCP server that delivers interactive knock-knock jokes on port 6789.// (Why is six afraid of seven? Because seven ate nine!)constnet=require("net");constreadline=require("readline");// Create a Server object and start listening for connectionsletserver=net.createServer();server.listen(6789,()=>console.log("Delivering laughs on port 6789"));// When a client connects, tell them a knock-knock joke.server.on("connection",socket=>{tellJoke(socket).then(()=>socket.end())// When the joke is done, close the socket..catch((err)=>{console.error(err);// Log any errors that occur,socket.end();// but still close the socket!});});// These are all the jokes we know.constjokes={"Boo":"Don't cry...it's only a joke!","Lettuce":"Let us in! It's freezing out here!","A little old lady":"Wow, I didn't know you could yodel!"};// Interactively perform a knock-knock joke over this socket, without blocking.asyncfunctiontellJoke(socket){// Pick one of the jokes at randomletrandomElement=a=>a[Math.floor(Math.random()*a.length)];letwho=randomElement(Object.keys(jokes));letpunchline=jokes[who];// Use the readline module to read the user's input one line at a time.letlineReader=readline.createInterface({input:socket,output:socket,prompt:">> "});// A utility function to output a line of text to the client// and then (by default) display a prompt.functionoutput(text,prompt=true){socket.write(`${text}\r\n`);if(prompt)lineReader.prompt();}// Knock-knock jokes have a call-and-response structure.// We expect different input from the user at different stages and// take different action when we get that input at different stages.letstage=0;// Start the knock-knock joke off in the traditional way.output("Knock knock!");// Now read lines asynchronously from the client until the joke is done.forawait(letinputLineoflineReader){if(stage===0){if(inputLine.toLowerCase()==="who's there?"){// If the user gives the right response at stage 0// then tell the first part of the joke and go to stage 1.output(who);stage=1;}else{// Otherwise teach the user how to do knock-knock jokes.output('Please type "Who\'s there?".');}}elseif(stage===1){if(inputLine.toLowerCase()===`${who.toLowerCase()}who?`){// If the user's response is correct at stage 1, then// deliver the punchline and return since the joke is done.output(`${punchline}`,false);return;}else{// Make the user play along.output(`Please type "${who}who?".`);}}}}
$ nc localhost 6789 Knock knock! >> Who's there? A little old lady >> A little old lady who? Wow, I didn't know you could yodel!
// Connect to the joke port (6789) on the server named on the command lineletsocket=require("net").createConnection(6789,process.argv[2]);socket.pipe(process.stdout);// Pipe data from the socket to stdoutprocess.stdin.pipe(socket);// Pipe data from stdin to the socketsocket.on("close",()=>process.exit());// Quit when the socket closes.
const child_process = require("child_process");
let listing = child_process.execSync("ls -l web/*.html", {encoding: "utf8"});
let listing = child_process.execFileSync("ls", ["-l", "web/"],
{encoding: "utf8"});
constchild_process=require("child_process");constutil=require("util");constexecP=util.promisify(child_process.exec);functionparallelExec(commands){// Use the array of commands to create an array of Promisesletpromises=commands.map(command=>execP(command,{encoding:"utf8"}));// Return a Promise that will fulfill to an array of the fulfillment// values of each of the individual promises. (Instead of returning objects// with stdout and stderr properties we just return the stdout value.)returnPromise.all(promises).then(outputs=>outputs.map(out=>out.stdout));}module.exports=parallelExec;
constchild_process=require("child_process");// Start a new node process running the code in child.js in our directoryletchild=child_process.fork(`${__dirname}/child.js`);// Send a message to the childchild.send({x:4,y:3});// Print the child's response when it arrives.child.on("message",message=>{console.log(message.hypotenuse);// This should print "5"// Since we only send one message we only expect one response.// After we receive it we call disconnect() to terminate the connection// between parent and child. This allows both processes to exit cleanly.child.disconnect();});
// Wait for messages from our parent processprocess.on("message",message=>{// When we receive one, do a calculation and send the result// back to the parent.process.send({hypotenuse:Math.hypot(message.x,message.y)});});
If your application actually needs to do more computation than one CPU core can handle, then threads allow you to distribute work across the multiple cores, which have become commonplace on computers today. If you’re doing scientific computing or machine learning or graphics processing in Node, then you may want to use threads simply to throw more computing power at your problem.
Even if your application is not using the full power of one CPU, you may still want to use threads to maintain the responsiveness of the main thread. Consider a server that handles large but relatively infrequent requests. Suppose it gets only one request a second, but needs to spend about half a second of (blocking CPU-bound) computation to process each request. On average, it will be idle 50% of the time. But when two requests arrive within a few milliseconds of each other, the server will not even be able to begin a response to the second request until the computation of the first response is complete. Instead, if the server uses a worker thread to perform the computation, the server can begin the response to both requests immediately and provide a better experience for the server’s clients. Assuming the server has more than one CPU core, it can also compute the body of both responses in parallel, but even if there is only a single core, using workers still improves the responsiveness.
In general, workers allow us to turn blocking synchronous operations into nonblocking asynchronous operations. If you are writing a program that depends on legacy code that is unavoidably synchronous, you may be able to use workers to avoid blocking when you need to call that legacy code.
constthreads=require("worker_threads");
constthreads=require("worker_threads");// The worker_threads module exports the boolean isMainThread property.// This property is true when Node is running the main thread and it is// false when Node is running a worker. We can use this fact to implement// the main and worker threads in the same file.if(threads.isMainThread){// If we're running in the main thread, then all we do is export// a function. Instead of performing a computationally intensive// task on the main thread, this function passes the task to a worker// and returns a Promise that will resolve when the worker is done.module.exports=functionreticulateSplines(splines){returnnewPromise((resolve,reject)=>{// Create a worker that loads and runs this same file of code.// Note the use of the special __filename variable.letreticulator=newthreads.Worker(__filename);// Pass a copy of the splines array to the workerreticulator.postMessage(splines);// And then resolve or reject the Promise when we get// a message or error from the worker.reticulator.on("message",resolve);reticulator.on("error",reject);});};}else{// If we get here, it means we're in the worker, so we register a// handler to get messages from the main thread. This worker is designed// to only receive a single message, so we register the event handler// with once() instead of on(). This allows the worker to exit naturally// when its work is complete.threads.parentPort.once("message",splines=>{// When we get the splines from the parent thread, loop// through them and reticulate all of them.for(letsplineofsplines){// For the sake of example, assume that spline objects usually// have a reticulate() method that does a lot of computation.spline.reticulate?spline.reticulate():spline.reticulated=true;}// When all the splines have (finally!) been reticulated// pass a copy back to the main thread.threads.parentPort.postMessage(splines);});}
newthreads.Worker(`const threads = require("worker_threads");threads.parentPort.postMessage(threads.isMainThread);`,{eval:true}).on("message",console.log);// This will print "false"
As we’ve seen, threads.isMainThread is true in the main
thread but is always false in any worker thread.
In a worker thread, you can use threads.parentPort.postMessage()
to send a message to the parent thread and threads.parentPort.on
to register event handlers for messages from the parent thread. In
the main thread, threads.parentPort is always null.
In a worker thread, threads.workerData is set to a copy of the
workerData property of the second argument to the Worker()
constructor. In the main thread, this property is always null. You
can use this workerData property to pass an initial message to the
worker that will be available as soon as it starts so that the
worker does not have to wait for a “message” event before it can start
doing work.
By default, process.env in a worker thread is a copy of
process.env in the parent thread. But the parent thread can
specify a custom set of environment variables by setting the env
property of the second argument to the Worker() constructor. As a
special (and potentially dangerous) case, the parent thread can set
the env property to threads.SHARE_ENV, which will cause the two
threads to share a single set of environment variables so that a
change in one thread is visible in the other.
By default, the process.stdin stream in a worker never has any
readable data on it. You can change this default by passing stdin:
true in the second argument to the Worker() constructor. If you
do that, then the stdin property of the Worker object is a
Writable stream. Any data that the parent writes to worker.stdin
becomes readable on process.stdin in the worker.
By default, the process.stdout and process.stderr streams in the
worker are simply piped to the corresponding streams in the parent
thread. This means, for example, that console.log() and
console.error() produce output in exactly the same way in a worker
thread as they do in the main thread. You can override this default
by passing stdout:true or stderr:true in the second argument to
the Worker() constructor. If you do this, then any output the
worker writes to those streams becomes readable by the parent thread
on the worker.stdout and worker.stderr threads. (There is a
potentially confusing inversion of stream directions here, and we
saw the same thing with with child processes earlier in the chapter:
the output streams of a worker thread are input streams for the
parent thread, and the input stream of a worker is an output stream
for the parent.)
If a worker thread calls process.exit(), only the thread exits,
not the entire process.
Worker threads are not allowed to change shared state of the process
they are part of. Functions like process.chdir() and
process.setuid() will throw exceptions when invoked from a worker.
Operating system signals (like SIGINT and SIGTERM) are only
delivered to the main thread; they cannot be received or handled in
worker threads.
constthreads=require("worker_threads");letchannel=newthreads.MessageChannel();channel.port2.on("message",console.log);// Log any messages we receivechannel.port1.postMessage("hello");// Will cause "hello" to be printed
// Create a custom communication channelconstthreads=require("worker_threads");letchannel=newthreads.MessageChannel();// Use the worker's default channel to transfer one end of the new// channel to the worker. Assume that when the worker receives this// message it immediately begins to listen for messages on the new channel.worker.postMessage({command:"changeChannel",data:channel.port1},[channel.port1]);// Now send a message to the worker using our end of the custom channelchannel.port2.postMessage("Can you hear me now?");// And listen for responses from the worker as wellchannel.port2.on("message",handleMessagesFromWorker);
letpixels=newUint32Array(1024*1024);// 4 megabytes of memory// Assume we read some data into this typed array, and then transfer the// pixels to a worker without copying. Note that we don't put the array// itself in the transfer list, but the array's Buffer object instead.worker.postMessage(pixels,[pixels.buffer]);
constthreads=require("worker_threads");if(threads.isMainThread){// In the main thread, we create a shared typed array with// one element. Both threads will be able to read and write// sharedArray[0] at the same time.letsharedBuffer=newSharedArrayBuffer(4);letsharedArray=newInt32Array(sharedBuffer);// Now create a worker thread, passing the shared array to it with// as its initial workerData value so we don't have to bother with// sending and receiving a messageletworker=newthreads.Worker(__filename,{workerData:sharedArray});// Wait for the worker to start running and then increment the// shared integer 10 million times.worker.on("online",()=>{for(leti=0;i<10_000_000;i++)sharedArray[0]++;// Once we're done with our increments, we start listening for// message events so we know when the worker is done.worker.on("message",()=>{// Although the shared integer has been incremented// 20 million times, its value will generally be much less.// On my computer the final value is typically under 12 million.console.log(sharedArray[0]);});});}else{// In the worker thread, we get the shared array from workerData// and then increment it 10 million times.letsharedArray=threads.workerData;for(leti=0;i<10_000_000;i++)sharedArray[0]++;// When we're done incrementing, let the main thread knowthreads.parentPort.postMessage("done");}
constthreads=require("worker_threads");if(threads.isMainThread){letsharedBuffer=newSharedArrayBuffer(4);letsharedArray=newInt32Array(sharedBuffer);letworker=newthreads.Worker(__filename,{workerData:sharedArray});worker.on("online",()=>{for(leti=0;i<10_000_000;i++){Atomics.add(sharedArray,0,1);// Threadsafe atomic increment}worker.on("message",(message)=>{// When both threads are done, use a threadsafe function// to read the shared array and confirm that it has the// expected value of 20,000,000.console.log(Atomics.load(sharedArray,0));});});}else{letsharedArray=threads.workerData;for(leti=0;i<10_000_000;i++){Atomics.add(sharedArray,0,1);// Threadsafe atomic increment}threads.parentPort.postMessage("done");}
Node’s asynchronous-by-default APIs and its single-threaded, callback, and event-based style of concurrency.
Node’s fundamental datatypes, buffers, and streams.
Node’s “fs” and “path” modules for working with the filesystem.
Node’s “http” and “https” modules for writing HTTP clients and servers.
Node’s “net” module for writing non-HTTP clients and servers.
Node’s “child_process” module for creating and communicating with child processes.
Node’s “worker_threads” module for true multithreaded programming using message-passing instead of shared memory.
1 Node defines a fs.copyFile() function that you would actually use in practice.
2 It is often cleaner and simpler to define the worker code in a separate file. But this trick of having two threads run different sections of the same file blew my mind when I first encountered it for the Unix fork() system call. And I think it is worth demonstrating this technique simply for its strange elegance.
ESLint for finding potential bugs and style problems in your code.
Prettier for formatting your JavaScript code in a standardized way.
Jest as an all-in-one solution for writing JavaScript unit tests.
npm for managing and installing the software libraries that your program depends on.
Code-bundling tools—like webpack, Rollup, and Parcel—that convert your modules of JavaScript code into a single bundle for use on the web.
Babel for translating JavaScript code that uses brand-new language features (or that uses language extensions) into JavaScript code that can run in current web browsers.
The JSX language extension (used by the React framework) that allows you to describe user interfaces using JavaScript expressions that look like HTML markup.
The Flow language extension (or the similar TypeScript extension) that allows you to annotate your JavaScript code with types and check your code for type safety.
varx='unused';exportfunctionfactorial(x){if(x==1){return1;}else{returnx*factorial(x-1)}}
$ eslint code/ch17/linty.js code/ch17/linty.js 1:1 error Unexpected var, use let or const instead no-var 1:5 error 'x' is assigned a value but never used no-unused-vars 1:9 warning Strings must use doublequote quotes 4:11 error Expected '===' and instead saw '==' eqeqeq 5:1 error Expected indentation of 8 spaces but found 6 indent 7:28 error Missing semicolon semi ✖ 6 problems (5 errors, 1 warning) 3 errors and 1 warning potentially fixable with the `--fix` option.
functionfactorial(x){if(x===1){return1}else{returnx*factorial(x-1)}}
$ prettier factorial.js
function factorial(x) {
if (x === 1) {
return 1;
} else {
return x * factorial(x - 1);
}
}
constgetJSON=require("./getJSON.js");/*** getTemperature() takes the name of a city as its input, and returns* a Promise that will resolve to the current temperature of that city,* in degrees Fahrenheit. It relies on a (fake) web service that returns* world temperatures in degrees Celsius.*/module.exports=asyncfunctiongetTemperature(city){// Get the temperature in Celsius from the web serviceletc=awaitgetJSON(`https://globaltemps.example.com/api/city/${city.toLowerCase()}`);// Convert to Fahrenheit and return that value.return(c*5/9)+32;// TODO: double-check this formula};
// Import the function we are going to testconstgetTemperature=require("./getTemperature.js");// And mock the getJSON() module that getTemperature() depends onjest.mock("./getJSON");constgetJSON=require("./getJSON.js");// Tell the mock getJSON() function to return an already resolved Promise// with fulfillment value 0.getJSON.mockResolvedValue(0);// Our set of tests for getTemperature() begins heredescribe("getTemperature()",()=>{// This is the first test. We're ensuring that getTemperature() calls// getJSON() with the URL that we expecttest("Invokes the correct API",async()=>{letexpectedURL="https://globaltemps.example.com/api/city/vancouver";lett=await(getTemperature("Vancouver"));// Jest mocks remember how they were called, and we can check that.expect(getJSON).toHaveBeenCalledWith(expectedURL);});// This second test verifies that getTemperature() converts// Celsius to Fahrenheit correctlytest("Converts C to F correctly",async()=>{getJSON.mockResolvedValue(0);// If getJSON returns 0Cexpect(awaitgetTemperature("x")).toBe(32);// We expect 32F// 100C should convert to 212FgetJSON.mockResolvedValue(100);// If getJSON returns 100Cexpect(awaitgetTemperature("x")).toBe(212);// We expect 212F});});
$ jest getTemperature
FAIL ch17/getTemperature.test.js
getTemperature()
✓ Invokes the correct API (4ms)
✕ Converts C to F correctly (3ms)
● getTemperature() › Converts C to F correctly
expect(received).toBe(expected) // Object.is equality
Expected: 212
Received: 87.55555555555556
29 | // 100C should convert to 212F
30 | getJSON.mockResolvedValue(100); // If getJSON returns 100C
> 31 | expect(await getTemperature("x")).toBe(212); // Expect 212F
| ^
32 | });
33 | });
34 |
at Object.<anonymous> (ch17/getTemperature.test.js:31:43)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 1.403s
Ran all test suites matching /getTemperature/i.
$ jest --coverage getTemperature
PASS ch17/getTemperature.test.js
getTemperature()
✓ Invokes the correct API (3ms)
✓ Converts C to F correctly (1ms)
------------------|--------|---------|---------|---------|------------------|
File | % Stmts| % Branch| % Funcs| % Lines| Uncovered Line #s|
------------------|--------|---------|---------|---------|------------------|
All files | 71.43| 100| 33.33| 83.33| |
getJSON.js | 33.33| 100| 0| 50| 2|
getTemperature.js| 100| 100| 100| 100| |
------------------|--------|---------|---------|---------|------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.508s
Ran all test suites matching /getTemperature/i.
$ npm install express
$ npm install --save-dev prettier
$ npm install -g eslint jest /usr/local/bin/eslint -> /usr/local/lib/node_modules/eslint/bin/eslint.js /usr/local/bin/jest -> /usr/local/lib/node_modules/jest/bin/jest.js + jest@24.9.0 + eslint@6.7.2 added 653 packages from 414 contributors in 25.596s $ which eslint /usr/local/bin/eslint $ which jest /usr/local/bin/jest
$ npm audit --fix
=== npm audit security report ===
found 0 vulnerabilities
in 876354 scanned packages
Some programs have more than one entry point. A web application with multiple pages, for example, could be written with a different entry point for each page. Bundlers generally allow you to create one bundle per entry point or to create a single bundle that supports multiple entry points.
Programs can use import() in its functional form (§10.3.6)
instead of its static form to dynamically load modules when they are
actually needed rather than statically loading them at program
startup time. Doing this is often a good way to improve the startup
time for your program. Bundler tools that support import() may be
able to produce multiple output bundles: one to load at startup
time, and one or more that are loaded dynamically when needed. This
can work well if there are only a few calls to import() in your
program and they load modules with relatively disjoint sets of
dependencies. If the dynamically loaded modules share dependencies
then it becomes tricky to figure out how many bundles to produce,
and you are likely to have to manually configure your bundler to
sort this out.
Bundlers can generally output a source map file that defines a mapping between the lines of code in the bundle and the corresponding lines in the original source files. This allows browser developer tools to automatically display JavaScript errors at their original unbundled locations.
Sometimes when you import a module into your program, you only use a few of its features. A good bundler tool can analyze the code to determine which parts are unused and can be omitted from the bundles. This feature goes by the whimsical name of “tree-shaking.”
Bundlers typically have a plug-in–based architecture and support
plug-ins that allow importing and bundling “modules” that are not
actually files of JavaScript code. Suppose that your program
includes a large JSON-compatible data structure. Code bundlers can
be configured to allow you to move that data structure into a
separate JSON file and then import it into your program with a
declaration like import widgets from
"./big-widget-list.json". Similarly, web developers who embed CSS
into their JavaScript programs can use bundler plug-ins that allow
them to import CSS files with an import directive. Note, however,
that if you import anything other than a JavaScript file, you are
using a nonstandard JavaScript extension and making your code
dependent on the bundler tool.
In a language like JavaScript that does not require compilation, running a bundler tool feels like a compilation step, and it is frustrating to have to run a bundler after every code edit before you can run the code in your browser. Bundlers typically support filesystem watchers that detect edits to any files in a project directory and automatically regenerate the necessary bundles. With this feature in place you can typically save your code and then immediately reload your web browser window to try it out.
Some bundlers also support a “hot module replacement” mode for developers where each time a bundle is regenerated, it is automatically loaded into the browser. When this works, it is a magical experience for developers, but there are some tricks going on under the hood to make it work, and it is not suitable for all projects.
letline=<hr/>;
letline=React.createElement("hr",null);
letimage=<imgsrc="logo.png"alt="The JSX logo"hidden/>;
letimage=React.createElement("img",{src:"logo.png",alt:"The JSX logo",hidden:true});
letsidebar=(<divclassName="sidebar"><h1>Title</h1><hr/><p>Thisisthesidebarcontent</p></div>);
letsidebar=React.createElement("div",{className:"sidebar"},// This outer call creates a <div>React.createElement("h1",null,// This is the first child of the <div/>"Title"),// and its own first child.React.createElement("hr",null),// The second child of the <div/>.React.createElement("p",null,// And the third child."This is the sidebar content"));
functionsidebar(className,title,content,drawLine=true){return(<divclassName={className}><h1>{title}</h1>{drawLine&&<hr/>}<p>{content}</p></div>);}
functionsidebar(className,title,content,drawLine=true){returnReact.createElement("div",{className:className},React.createElement("h1",null,title),drawLine&&React.createElement("hr",null),React.createElement("p",null,content));}
// Given an array of strings and a callback function return a JSX element// representing an HTML <ul> list with an array of <li> elements as its child.functionlist(items,callback){return(<ulstyle={{padding:10,border:"solid red 4px"}}>{items.map((item,index)=>{<lionClick={()=>callback(index)}key={index}>{item}</li>})}</ul>);}
functionlist(items,callback){returnReact.createElement("ul",{style:{padding:10,border:"solid red 4px"}},items.map((item,index)=>React.createElement("li",{onClick:()=>callback(index),key:index},item)));}
lethebrew={lang:"he",dir:"rtl"};// Specify language and directionletshalom=<spanclassName="emphasis"{...hebrew}>שלום</span>;
letshalom=React.createElement("span",_extends({className:"emphasis"},hebrew),"\u05E9\u05DC\u05D5\u05DD");
functionSidebar(props){return(<div><h1>{props.title}</h1>{props.drawLine&&<hr/>}<p>{props.content}</p></div>);}
letsidebar=<Sidebartitle="Something snappy"content="Something wise"/>;
letsidebar=React.createElement(Sidebar,{title:"Something snappy",content:"Something wise"});
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ variableReassignment.js:6:3
Cannot assign 1 to i.r because:
• property r is missing in number [1].
2│ let i = { r: 0, i: 1 }; // The complex number 0+1i
[1] 3│ for(i = 0; i < 10; i++) { // Oops! The loop variable overwrites i
4│ console.log(i);
5│ }
6│ i.r = 1; // Flow detects the error here
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ size.js:3:14
Cannot get x.length because property length is missing in Number [1].
1│ // @flow
2│ function size(x) {
3│ return x.length;
4│ }
[1] 5│ let s = size(1000);
letmessage:string="Hello world";letflag:boolean=false;letn:number=42;
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ size2.js:5:18
Cannot call size with array literal bound to s because array literal [1]
is incompatible with string [2].
[2] 2│ function size(s: string): number {
3│ return s.length;
4│ }
[1] 5│ console.log(size([1,2,3]));
constsize=(s:string):number=>s.length;
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ size3.js:3:18
Cannot call size with null bound to s because null [1] is incompatible
with string [2].
1│ // @flow
[2] 2│ const size = (s: string): number => s.length;
[1] 3│ console.log(size(null));
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ size4.js:3:14
Cannot get s.length because property length is missing in null or
undefined [1].
1│ // @flow
[1] 2│ function size(s: ?string): number {
3│ return s.length;
4│ }
5│ console.log(size(null));
functionsize(s:?string):number{// At this point in the code, s could be a string or null or undefined.if(s===null||s===undefined){// In this block, Flow knows that s is null or undefined.return-1;}else{// And in this block, Flow knows that s is a string.returns.length;}}
// @flow// Return true if the ISO representation of the specified date// matches the specified pattern, or false otherwise.// E.g: const isTodayChristmas = dateMatches(new Date(), /^\d{4}-12-25T/);exportfunctiondateMatches(d:Date,p:RegExp):boolean{returnp.test(d.toISOString());}
// @flowexportdefaultclassComplex{// Flow requires an extended class syntax that includes type annotations// for each of the properties used by the class.i:number;r:number;statici:Complex;constructor(r:number,i:number){// Any properties initialized by the constructor must have Flow type// annotations above.this.r=r;this.i=i;}add(that:Complex){returnnewComplex(this.r+that.r,this.i+that.i);}}// This assignment would not be allowed by Flow if there was not a// type annotation for i inside the class.Complex.i=newComplex(0,1);
// @flow// Given an object with numeric x and y properties, return the// distance from the origin to the point (x,y) as a number.exportdefaultfunctiondistance(point:{x:number,y:number}):number{returnMath.hypot(point.x,point.y);}
{x:number,y:number,z?:number}
{|x:number,y:number|}
// @flowconstcityLocations:{[string]:{longitude:number,latitude:number}}={"Seattle":{longitude:47.6062,latitude:-122.3321},// TODO: if there are any other important cities, add them here.};exportdefaultcityLocations;
// @flowexporttypePoint={x:number,y:number};// Given a Point object return its distance from the originexportdefaultfunctiondistance(point:Point):number{returnMath.hypot(point.x,point.y);}
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ average.js:8:16
Cannot call average with array literal bound to data because string [1]
is incompatible with number [2] in array element.
[2] 2│ function average(data: Array<number>) {
3│ let sum = 0;
4│ for(let x of data) sum += x;
5│ return sum/data.length;
6│ }
7│
[1] 8│ average([1, 2, "three"]);
functiongetStatus():[number,string]{return[getStatusCode(),getStatusMessage()];}
let[code,message]=getStatus();
// @flowexporttypeColor=[number,number,number,number];// [r, g, b, opacity]functiongray(level:number):Color{return[level,level,level,1];}functionfade([r,g,b,a]:Color,factor:number):Color{return[r,g,b,a/factor];}let[r,g,b,a]=fade(gray(75),3);
// @flowfunctionsize(s:Array<mixed>):number{returns.length;}console.log(size([1,true,"three"]));
// @flow// Return a set of numbers with members that are exactly twice those// of the input set of numbers.functiondouble(s:Set<number>):Set<number>{letdoubled:Set<number>=newSet();for(letnofs)doubled.add(n*2);returndoubled;}console.log(double(newSet([1,2,3])));// Prints "Set {2, 4, 6}"
// @flowimporttype{Color}from"./Color.js";letcolorNames:Map<string,Color>=newMap([["red",[1,0,0,1]],["green",[0,1,0,1]],["blue",[0,0,1,1]]]);
letresult:Result<TypeError,Set<string>>;
// @flow// This class represents the result of an operation that can either// throw an error of type E or a value of type V.exportclassResult<E,V>{error:?E;value:?V;constructor(error:?E,value:?V){this.error=error;this.value=value;}threw():?E{returnthis.error;}returned():?V{returnthis.value;}get():V{if(this.error){throwthis.error;}elseif(this.value===null||this.value===undefined){thrownewTypeError("Error and value must not both be null");}else{returnthis.value;}}}
// @flow// Combine the elements of two arrays into an array of pairsfunctionzip<A,B>(a:Array<A>,b:Array<B>):Array<[?A,?B]>{letresult:Array<[?A,?B]>=[];letlen=Math.max(a.length,b.length);for(leti=0;i<len;i++){result.push([a[i],b[i]]);}returnresult;}// Create the array [[1,'a'], [2,'b'], [3,'c'], [4,undefined]]letpairs:Array<[?number,?string]>=zip([1,2,3,4],['a','b','c'])
// @flowtypePoint={x:number,y:number};// This function takes a Point object but promises not to modify itfunctiondistance(p:$ReadOnly<Point>):number{returnMath.hypot(p.x,p.y);}letp:Point={x:3,y:4};distance(p)// => 5// This function takes an array of numbers that it will not modifyfunctionaverage(data:$ReadOnlyArray<number>):number{letsum=0;for(leti=0;i<data.length;i++)sum+=data[i];returnsum/data.length;}letdata:Array<number>=[1,2,3,4,5];average(data)// => 3
// @flow// The type of the callback function used in fetchText() belowexporttypeFetchTextCallback=(?Error,?number,?string)=>void;exportdefaultfunctionfetchText(url:string,callback:FetchTextCallback){letstatus=null;fetch(url).then(response=>{status=response.status;returnresponse.text()}).then(body=>{callback(null,status,body);}).catch(error=>{callback(error,status,null);});}
// @flowfunctionsize(collection:Array<mixed>|Set<mixed>|Map<mixed,mixed>):number{if(Array.isArray(collection)){returncollection.length;}else{returncollection.size;}}size([1,true,"three"])+size(newSet([true,false]))// => 5
typeAnswer="yes"|"no";typeDigit=0|1|2|3|4|5|6|7|8|9;
leta:Answer="Yes".toLowerCase();// Error: can't assign string to Answerletd:Digit=3+4;// Error: can't assign number to Digit
typeSuit="Clubs"|"Diamonds"|"Hearts"|"Spades";
typeHTTPStatus=|200// OK|304// Not Modified|403// Forbidden|404;// Not Found
// @flow// The worker sends a message of this type when it is done// reticulating the splines we sent it.exporttypeResultMessage={messageType:"result",result:Array<ReticulatedSpline>,// Assume this type is defined elsewhere.};// The worker sends a message of this type if its code failed with an exception.exporttypeErrorMessage={messageType:"error",error:Error,};// The worker sends a message of this type to report usage statistics.exporttypeStatisticsMessage={messageType:"stats",splinesReticulated:number,splinesPerSecond:number};// When we receive a message from the worker it will be a WorkerMessage.exporttypeWorkerMessage=ResultMessage|ErrorMessage|StatisticsMessage;// The main thread will have an event handler function that is passed// a WorkerMessage. But because we've carefully defined each of the// message types to have a messageType property with a literal type,// the event handler can easily discriminate among the possible messages:functionhandleMessageFromReticulator(message:WorkerMessage){if(message.messageType==="result"){// Only ResultMessage has a messageType property with this value// so Flow knows that it is safe to use message.result here.// And Flow will complain if you try to use any other property.console.log(message.result);}elseif(message.messageType==="error"){// Only ErrorMessage has a messageType property with value "error"// so knows that it is safe to use message.error here.throwmessage.error;}elseif(message.messageType==="stats"){// Only StatisticsMessage has a messageType property with value "stats"// so knows that it is safe to use message.splinesPerSecond here.console.info(message.splinesPerSecond);}}
1 If you have programmed with Java, you may have experienced something like this the first time you wrote a generic API that used a type parameter. I found the learning process for Flow to be remarkably similar to what I went through in 2004 when generics were added to Java.